systemd: merge branch systemd into main

https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/945
This commit is contained in:
Thomas Haller 2021-07-29 19:07:00 +02:00
commit ce3069b01e
No known key found for this signature in database
GPG key ID: 29C2366E4DFC5728
42 changed files with 992 additions and 1069 deletions

View file

@ -61,6 +61,8 @@
/*****************************************************************************/
#define HAVE_OPENSSL 0
static inline int
sd_notify(int unset_environment, const char *state)
{

View file

@ -10,11 +10,13 @@
#include <netinet/if_ether.h>
#include "arp-util.h"
#include "ether-addr-util.h"
#include "fd-util.h"
#include "in-addr-util.h"
#include "unaligned.h"
#include "util.h"
int arp_network_bind_raw_socket(int ifindex, be32_t address, const struct ether_addr *eth_mac) {
int arp_update_filter(int fd, const struct in_addr *a, const struct ether_addr *eth_mac) {
struct sock_filter filter[] = {
BPF_STMT(BPF_LD + BPF_W + BPF_LEN, 0), /* A <- packet length */
BPF_JUMP(BPF_JMP + BPF_JGE + BPF_K, sizeof(struct ether_arp), 1, 0), /* packet >= arp packet ? */
@ -48,13 +50,13 @@ int arp_network_bind_raw_socket(int ifindex, be32_t address, const struct ether_
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 0, 1), /* A == 0 ? */
BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
/* Sender Protocol Address or Target Protocol Address must be equal to the one we care about */
BPF_STMT(BPF_LD + BPF_IMM, htobe32(address)), /* A <- clients IP */
BPF_STMT(BPF_LD + BPF_IMM, htobe32(a->s_addr)), /* A <- clients IP */
BPF_STMT(BPF_MISC + BPF_TAX, 0), /* X <- A */
BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(struct ether_arp, arp_spa)), /* A <- SPA */
BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0), /* X xor A */
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 0, 1), /* A == 0 ? */
BPF_STMT(BPF_RET + BPF_K, 65535), /* return all */
BPF_STMT(BPF_LD + BPF_IMM, htobe32(address)), /* A <- clients IP */
BPF_STMT(BPF_LD + BPF_IMM, htobe32(a->s_addr)), /* A <- clients IP */
BPF_STMT(BPF_MISC + BPF_TAX, 0), /* X <- A */
BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(struct ether_arp, arp_tpa)), /* A <- TPA */
BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0), /* X xor A */
@ -63,15 +65,25 @@ int arp_network_bind_raw_socket(int ifindex, be32_t address, const struct ether_
BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
};
struct sock_fprog fprog = {
.len = ELEMENTSOF(filter),
.filter = (struct sock_filter*) filter
.len = ELEMENTSOF(filter),
.filter = (struct sock_filter*) filter,
};
assert(fd >= 0);
if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog)) < 0)
return -errno;
return 0;
}
int arp_network_bind_raw_socket(int ifindex, const struct in_addr *a, const struct ether_addr *eth_mac) {
union sockaddr_union link = {
.ll.sll_family = AF_PACKET,
.ll.sll_family = AF_PACKET,
.ll.sll_protocol = htobe16(ETH_P_ARP),
.ll.sll_ifindex = ifindex,
.ll.sll_halen = ETH_ALEN,
.ll.sll_addr = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff },
.ll.sll_ifindex = ifindex,
.ll.sll_halen = ETH_ALEN,
.ll.sll_addr = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff },
};
_cleanup_close_ int s = -1;
int r;
@ -82,59 +94,57 @@ int arp_network_bind_raw_socket(int ifindex, be32_t address, const struct ether_
if (s < 0)
return -errno;
r = setsockopt(s, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog));
r = arp_update_filter(s, a, eth_mac);
if (r < 0)
return -errno;
return r;
r = bind(s, &link.sa, sizeof(link.ll));
if (r < 0)
if (bind(s, &link.sa, sizeof(link.ll)) < 0)
return -errno;
return TAKE_FD(s);
}
static int arp_send_packet(int fd, int ifindex,
be32_t pa, const struct ether_addr *ha,
bool announce) {
int arp_send_packet(
int fd,
int ifindex,
const struct in_addr *pa,
const struct ether_addr *ha,
bool announce) {
union sockaddr_union link = {
.ll.sll_family = AF_PACKET,
.ll.sll_family = AF_PACKET,
.ll.sll_protocol = htobe16(ETH_P_ARP),
.ll.sll_ifindex = ifindex,
.ll.sll_halen = ETH_ALEN,
.ll.sll_addr = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff },
.ll.sll_ifindex = ifindex,
.ll.sll_halen = ETH_ALEN,
.ll.sll_addr = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff },
};
struct ether_arp arp = {
.ea_hdr.ar_hrd = htobe16(ARPHRD_ETHER), /* HTYPE */
.ea_hdr.ar_pro = htobe16(ETHERTYPE_IP), /* PTYPE */
.ea_hdr.ar_hln = ETH_ALEN, /* HLEN */
.ea_hdr.ar_pln = sizeof(be32_t), /* PLEN */
.ea_hdr.ar_op = htobe16(ARPOP_REQUEST), /* REQUEST */
.ea_hdr.ar_hrd = htobe16(ARPHRD_ETHER), /* HTYPE */
.ea_hdr.ar_pro = htobe16(ETHERTYPE_IP), /* PTYPE */
.ea_hdr.ar_hln = ETH_ALEN, /* HLEN */
.ea_hdr.ar_pln = sizeof(struct in_addr), /* PLEN */
.ea_hdr.ar_op = htobe16(ARPOP_REQUEST), /* REQUEST */
};
int r;
ssize_t n;
assert(fd >= 0);
assert(pa != 0);
assert(ifindex > 0);
assert(pa);
assert(in4_addr_is_set(pa));
assert(ha);
assert(!ether_addr_is_null(ha));
memcpy(&arp.arp_sha, ha, ETH_ALEN);
memcpy(&arp.arp_tpa, &pa, sizeof(pa));
memcpy(&arp.arp_tpa, pa, sizeof(struct in_addr));
if (announce)
memcpy(&arp.arp_spa, &pa, sizeof(pa));
memcpy(&arp.arp_spa, pa, sizeof(struct in_addr));
r = sendto(fd, &arp, sizeof(struct ether_arp), 0, &link.sa, sizeof(link.ll));
if (r < 0)
n = sendto(fd, &arp, sizeof(struct ether_arp), 0, &link.sa, sizeof(link.ll));
if (n < 0)
return -errno;
if (n != sizeof(struct ether_arp))
return -EIO;
return 0;
}
int arp_send_probe(int fd, int ifindex,
be32_t pa, const struct ether_addr *ha) {
return arp_send_packet(fd, ifindex, pa, ha, false);
}
int arp_send_announcement(int fd, int ifindex,
be32_t pa, const struct ether_addr *ha) {
return arp_send_packet(fd, ifindex, pa, ha, true);
}

View file

@ -6,13 +6,31 @@
***/
#include <net/ethernet.h>
#include <netinet/in.h>
#include "socket-util.h"
#include "sparse-endian.h"
int arp_network_bind_raw_socket(int index, be32_t address, const struct ether_addr *eth_mac);
int arp_update_filter(int fd, const struct in_addr *a, const struct ether_addr *eth_mac);
int arp_network_bind_raw_socket(int ifindex, const struct in_addr *a, const struct ether_addr *eth_mac);
int arp_send_probe(int fd, int ifindex,
be32_t pa, const struct ether_addr *ha);
int arp_send_announcement(int fd, int ifindex,
be32_t pa, const struct ether_addr *ha);
int arp_send_packet(
int fd,
int ifindex,
const struct in_addr *pa,
const struct ether_addr *ha,
bool announce);
static inline int arp_send_probe(
int fd,
int ifindex,
const struct in_addr *pa,
const struct ether_addr *ha) {
return arp_send_packet(fd, ifindex, pa, ha, false);
}
static inline int arp_send_announcement(
int fd,
int ifindex,
const struct in_addr *pa,
const struct ether_addr *ha) {
return arp_send_packet(fd, ifindex, pa, ha, true);
}

View file

@ -240,7 +240,7 @@ int sd_dhcp_client_set_callback(
int sd_dhcp_client_set_request_broadcast(sd_dhcp_client *client, int broadcast) {
assert_return(client, -EINVAL);
client->request_broadcast = !!broadcast;
client->request_broadcast = broadcast;
return 0;
}
@ -1592,18 +1592,17 @@ static int client_handle_forcerenew(sd_dhcp_client *client, DHCPMessage *force,
if (r != DHCP_FORCERENEW)
return -ENOMSG;
#if 0 /* NM_IGNORED */
#if 0
log_dhcp_client(client, "FORCERENEW");
return 0;
#else /* NM_IGNORED */
/* NM: patch out the handling of FORCERENEW. We don't implement rfc3118 (Authentication
* for DHCP Messages) nor rfc6704 (Forcerenew Nonce Authentication) so accepting
* unauthenticated FORCERENEW requests is a security issue (CVE-2020-13529)
* See: https://github.com/systemd/systemd/issues/16774 */
log_dhcp_client(client, "ignore FORCERENEW");
#else
/* FIXME: Ignore FORCERENEW requests until we implement RFC3118 (Authentication for DHCP
* Messages) and/or RFC6704 (Forcerenew Nonce Authentication), as unauthenticated FORCERENEW
* requests causes a security issue (TALOS-2020-1142, CVE-2020-13529). */
log_dhcp_client(client, "Received FORCERENEW, ignoring.");
return -ENOMSG;
#endif /* NM_IGNORED */
#endif
}
static bool lease_equal(const sd_dhcp_lease *a, const sd_dhcp_lease *b) {
@ -1692,7 +1691,6 @@ static int client_handle_ack(sd_dhcp_client *client, DHCPMessage *ack, size_t le
static int client_set_lease_timeouts(sd_dhcp_client *client) {
usec_t time_now;
char time_string[FORMAT_TIMESPAN_MAX];
int r;
assert(client);
@ -1754,7 +1752,7 @@ static int client_set_lease_timeouts(sd_dhcp_client *client) {
return 0;
log_dhcp_client(client, "lease expires in %s",
format_timespan(time_string, FORMAT_TIMESPAN_MAX, client->expire_time - time_now, USEC_PER_SEC));
FORMAT_TIMESPAN(client->expire_time - time_now, USEC_PER_SEC));
/* arm T2 timeout */
r = event_reset_time(client->event, &client->timeout_t2,
@ -1770,7 +1768,7 @@ static int client_set_lease_timeouts(sd_dhcp_client *client) {
return 0;
log_dhcp_client(client, "T2 expires in %s",
format_timespan(time_string, FORMAT_TIMESPAN_MAX, client->t2_time - time_now, USEC_PER_SEC));
FORMAT_TIMESPAN(client->t2_time - time_now, USEC_PER_SEC));
/* arm T1 timeout */
r = event_reset_time(client->event, &client->timeout_t1,
@ -1783,15 +1781,14 @@ static int client_set_lease_timeouts(sd_dhcp_client *client) {
if (client->t1_time > time_now)
log_dhcp_client(client, "T1 expires in %s",
format_timespan(time_string, FORMAT_TIMESPAN_MAX, client->t1_time - time_now, USEC_PER_SEC));
FORMAT_TIMESPAN(client->t1_time - time_now, USEC_PER_SEC));
return 0;
}
static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, int len) {
DHCP_CLIENT_DONT_DESTROY(client);
char time_string[FORMAT_TIMESPAN_MAX];
int r = 0, notify_event = 0;
int r, notify_event;
assert(client);
assert(client->event);
@ -1801,22 +1798,19 @@ static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, i
case DHCP_STATE_SELECTING:
r = client_handle_offer(client, message, len);
if (r >= 0) {
if (r == -ENOMSG)
return 0; /* invalid message, let's ignore it */
if (r < 0)
goto error;
client->state = DHCP_STATE_REQUESTING;
client->attempt = 0;
r = event_reset_time(client->event, &client->timeout_resend,
clock_boottime_or_monotonic(),
0, 0,
client_timeout_resend, client,
client->event_priority, "dhcp4-resend-timer", true);
if (r < 0)
goto error;
} else if (r == -ENOMSG)
/* invalid message, let's ignore it */
return 0;
client->state = DHCP_STATE_REQUESTING;
client->attempt = 0;
r = event_reset_time(client->event, &client->timeout_resend,
clock_boottime_or_monotonic(),
0, 0,
client_timeout_resend, client,
client->event_priority, "dhcp4-resend-timer", true);
break;
case DHCP_STATE_REBOOTING:
@ -1825,47 +1819,9 @@ static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, i
case DHCP_STATE_REBINDING:
r = client_handle_ack(client, message, len);
if (r >= 0) {
client->start_delay = 0;
(void) event_source_disable(client->timeout_resend);
client->receive_message =
sd_event_source_unref(client->receive_message);
client->fd = safe_close(client->fd);
if (IN_SET(client->state, DHCP_STATE_REQUESTING,
DHCP_STATE_REBOOTING))
notify_event = SD_DHCP_CLIENT_EVENT_IP_ACQUIRE;
else if (r != SD_DHCP_CLIENT_EVENT_IP_ACQUIRE)
notify_event = r;
client->state = DHCP_STATE_BOUND;
client->attempt = 0;
client->last_addr = client->lease->address;
r = client_set_lease_timeouts(client);
if (r < 0) {
log_dhcp_client(client, "could not set lease timeouts");
goto error;
}
r = dhcp_network_bind_udp_socket(client->ifindex, client->lease->address, client->port, client->ip_service_type);
if (r < 0) {
log_dhcp_client(client, "could not bind UDP socket");
goto error;
}
client->fd = r;
client_initialize_io_events(client, client_receive_message_udp);
if (notify_event) {
client_notify(client, notify_event);
if (client->state == DHCP_STATE_STOPPED)
return 0;
}
} else if (r == -EADDRNOTAVAIL) {
if (r == -ENOMSG)
return 0; /* invalid message, let's ignore it */
if (r == -EADDRNOTAVAIL) {
/* got a NAK, let's restart the client */
client_notify(client, SD_DHCP_CLIENT_EVENT_EXPIRED);
@ -1877,39 +1833,75 @@ static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, i
if (r < 0)
goto error;
log_dhcp_client(client, "REBOOT in %s", format_timespan(time_string, FORMAT_TIMESPAN_MAX,
client->start_delay, USEC_PER_SEC));
log_dhcp_client(client, "REBOOT in %s", FORMAT_TIMESPAN(client->start_delay, USEC_PER_SEC));
client->start_delay = CLAMP(client->start_delay * 2,
RESTART_AFTER_NAK_MIN_USEC, RESTART_AFTER_NAK_MAX_USEC);
return 0;
} else if (r == -ENOMSG)
/* invalid message, let's ignore it */
return 0;
}
if (r < 0)
goto error;
if (IN_SET(client->state, DHCP_STATE_REQUESTING, DHCP_STATE_REBOOTING))
notify_event = SD_DHCP_CLIENT_EVENT_IP_ACQUIRE;
else
notify_event = r;
client->start_delay = 0;
(void) event_source_disable(client->timeout_resend);
client->receive_message = sd_event_source_unref(client->receive_message);
client->fd = safe_close(client->fd);
client->state = DHCP_STATE_BOUND;
client->attempt = 0;
client->last_addr = client->lease->address;
r = client_set_lease_timeouts(client);
if (r < 0) {
log_dhcp_client(client, "could not set lease timeouts");
goto error;
}
r = dhcp_network_bind_udp_socket(client->ifindex, client->lease->address, client->port, client->ip_service_type);
if (r < 0) {
log_dhcp_client(client, "could not bind UDP socket");
goto error;
}
client->fd = r;
client_initialize_io_events(client, client_receive_message_udp);
if (IN_SET(client->state, DHCP_STATE_RENEWING, DHCP_STATE_REBINDING) &&
notify_event == SD_DHCP_CLIENT_EVENT_IP_ACQUIRE)
/* FIXME: hmm, maybe this is a bug... */
log_dhcp_client(client, "client_handle_ack() returned SD_DHCP_CLIENT_EVENT_IP_ACQUIRE while DHCP client is %s the address, skipping callback.",
client->state == DHCP_STATE_RENEWING ? "renewing" : "rebinding");
else
client_notify(client, notify_event);
break;
case DHCP_STATE_BOUND:
r = client_handle_forcerenew(client, message, len);
if (r >= 0) {
r = client_timeout_t1(NULL, 0, client);
if (r < 0)
goto error;
} else if (r == -ENOMSG)
/* invalid message, let's ignore it */
return 0;
if (r == -ENOMSG)
return 0; /* invalid message, let's ignore it */
if (r < 0)
goto error;
r = client_timeout_t1(NULL, 0, client);
break;
case DHCP_STATE_INIT:
case DHCP_STATE_INIT_REBOOT:
r = 0;
break;
case DHCP_STATE_STOPPED:
r = -EINVAL;
goto error;
default:
assert_not_reached("invalid state");
}
error:

View file

@ -956,7 +956,6 @@ static int client_timeout_resend(sd_event_source *s, uint64_t usec, void *userda
usec_t time_now, init_retransmit_time = 0, max_retransmit_time = 0;
usec_t max_retransmit_duration = 0;
uint8_t max_retransmit_count = 0;
char time_string[FORMAT_TIMESPAN_MAX];
assert(s);
assert(client);
@ -1052,7 +1051,7 @@ static int client_timeout_resend(sd_event_source *s, uint64_t usec, void *userda
}
log_dhcp6_client(client, "Next retransmission in %s",
format_timespan(time_string, FORMAT_TIMESPAN_MAX, client->retransmit_time, USEC_PER_SEC));
FORMAT_TIMESPAN(client->retransmit_time, USEC_PER_SEC));
r = event_reset_time(client->event, &client->timeout_resend,
clock_boottime_or_monotonic(),
@ -1576,7 +1575,6 @@ static int client_get_lifetime(sd_dhcp6_client *client, uint32_t *lifetime_t1,
static int client_start(sd_dhcp6_client *client, enum DHCP6State state) {
int r;
usec_t timeout, time_now;
char time_string[FORMAT_TIMESPAN_MAX];
uint32_t lifetime_t1, lifetime_t2;
assert_return(client, -EINVAL);
@ -1649,8 +1647,7 @@ static int client_start(sd_dhcp6_client *client, enum DHCP6State state) {
timeout = client_timeout_compute_random(lifetime_t1 * USEC_PER_SEC);
log_dhcp6_client(client, "T1 expires in %s",
format_timespan(time_string, FORMAT_TIMESPAN_MAX, timeout, USEC_PER_SEC));
log_dhcp6_client(client, "T1 expires in %s", FORMAT_TIMESPAN(timeout, USEC_PER_SEC));
r = event_reset_time(client->event, &client->timeout_t1,
clock_boottime_or_monotonic(),
@ -1662,8 +1659,7 @@ static int client_start(sd_dhcp6_client *client, enum DHCP6State state) {
timeout = client_timeout_compute_random(lifetime_t2 * USEC_PER_SEC);
log_dhcp6_client(client, "T2 expires in %s",
format_timespan(time_string, FORMAT_TIMESPAN_MAX, timeout, USEC_PER_SEC));
log_dhcp6_client(client, "T2 expires in %s", FORMAT_TIMESPAN(timeout, USEC_PER_SEC));
r = event_reset_time(client->event, &client->timeout_t2,
clock_boottime_or_monotonic(),

View file

@ -20,6 +20,7 @@
#include "fd-util.h"
#include "in-addr-util.h"
#include "log-link.h"
#include "memory-util.h"
#include "network-common.h"
#include "random-util.h"
#include "siphash24.h"
@ -66,7 +67,7 @@ struct sd_ipv4acd {
sd_event_source *timer_event_source;
usec_t defend_window;
be32_t address;
struct in_addr address;
/* External */
struct ether_addr mac_addr;
@ -74,7 +75,9 @@ struct sd_ipv4acd {
sd_event *event;
int event_priority;
sd_ipv4acd_callback_t callback;
void* userdata;
void *userdata;
sd_ipv4acd_check_mac_callback_t check_mac_callback;
void *check_mac_userdata;
};
#define log_ipv4acd_errno(acd, error, fmt, ...) \
@ -210,18 +213,6 @@ static int ipv4acd_set_next_wakeup(sd_ipv4acd *acd, usec_t usec, usec_t random_u
acd->event_priority, "ipv4acd-timer", true);
}
static bool ipv4acd_arp_conflict(sd_ipv4acd *acd, struct ether_arp *arp) {
assert(acd);
assert(arp);
/* see the BPF */
if (memcmp(arp->arp_spa, &acd->address, sizeof(acd->address)) == 0)
return true;
/* the TPA matched instead of the SPA, this is not a conflict */
return false;
}
static int ipv4acd_on_timeout(sd_event_source *s, uint64_t usec, void *userdata) {
sd_ipv4acd *acd = userdata;
int r = 0;
@ -231,38 +222,32 @@ static int ipv4acd_on_timeout(sd_event_source *s, uint64_t usec, void *userdata)
switch (acd->state) {
case IPV4ACD_STATE_STARTED:
acd->defend_window = 0;
ipv4acd_set_state(acd, IPV4ACD_STATE_WAITING_PROBE, true);
if (acd->n_conflict >= MAX_CONFLICTS) {
char ts[FORMAT_TIMESPAN_MAX];
log_ipv4acd(acd, "Max conflicts reached, delaying by %s", format_timespan(ts, sizeof(ts), RATE_LIMIT_INTERVAL_USEC, 0));
log_ipv4acd(acd, "Max conflicts reached, delaying by %s",
FORMAT_TIMESPAN(RATE_LIMIT_INTERVAL_USEC, 0));
r = ipv4acd_set_next_wakeup(acd, RATE_LIMIT_INTERVAL_USEC, PROBE_WAIT_USEC);
if (r < 0)
goto fail;
} else {
} else
r = ipv4acd_set_next_wakeup(acd, 0, PROBE_WAIT_USEC);
if (r < 0)
goto fail;
}
if (r < 0)
goto fail;
break;
case IPV4ACD_STATE_WAITING_PROBE:
case IPV4ACD_STATE_PROBING:
/* Send a probe */
r = arp_send_probe(acd->fd, acd->ifindex, acd->address, &acd->mac_addr);
r = arp_send_probe(acd->fd, acd->ifindex, &acd->address, &acd->mac_addr);
if (r < 0) {
log_ipv4acd_errno(acd, r, "Failed to send ARP probe: %m");
goto fail;
} else {
_cleanup_free_ char *address = NULL;
union in_addr_union addr = { .in.s_addr = acd->address };
(void) in_addr_to_string(AF_INET, &addr, &address);
log_ipv4acd(acd, "Probing %s", strna(address));
}
log_ipv4acd(acd, "Probing "IPV4_ADDRESS_FMT_STR, IPV4_ADDRESS_FMT_VAL(acd->address));
if (acd->n_iteration < PROBE_NUM - 2) {
ipv4acd_set_state(acd, IPV4ACD_STATE_PROBING, false);
@ -288,12 +273,13 @@ static int ipv4acd_on_timeout(sd_event_source *s, uint64_t usec, void *userdata)
_fallthrough_;
case IPV4ACD_STATE_WAITING_ANNOUNCE:
/* Send announcement packet */
r = arp_send_announcement(acd->fd, acd->ifindex, acd->address, &acd->mac_addr);
r = arp_send_announcement(acd->fd, acd->ifindex, &acd->address, &acd->mac_addr);
if (r < 0) {
log_ipv4acd_errno(acd, r, "Failed to send ARP announcement: %m");
goto fail;
} else
log_ipv4acd(acd, "ANNOUNCE");
}
log_ipv4acd(acd, "Announcing "IPV4_ADDRESS_FMT_STR, IPV4_ADDRESS_FMT_VAL(acd->address));
ipv4acd_set_state(acd, IPV4ACD_STATE_ANNOUNCING, false);
@ -319,16 +305,45 @@ fail:
return 0;
}
static void ipv4acd_on_conflict(sd_ipv4acd *acd) {
_cleanup_free_ char *address = NULL;
union in_addr_union addr = { .in.s_addr = acd->address };
static bool ipv4acd_arp_conflict(sd_ipv4acd *acd, const struct ether_arp *arp, bool announced) {
assert(acd);
assert(arp);
/* RFC 5227 section 2.1.1.
* "the host receives any ARP packet (Request *or* Reply) on the interface where the probe is
* being performed, where the packet's 'sender IP address' is the address being probed for,
* then the host MUST treat this address as being in use by some other host" */
if (memcmp(arp->arp_spa, &acd->address, sizeof(struct in_addr)) == 0)
return true;
if (announced)
/* the TPA matched instead of SPA, this is not a conflict */
return false;
/* "any ARP Probe where the packet's 'target IP address' is the address being probed for, and
* the packet's 'sender hardware address' is not the hardware address of any of the host's
* interfaces, then the host SHOULD similarly treat this as an address conflict" */
if (arp->ea_hdr.ar_op != htobe16(ARPOP_REQUEST))
return false; /* not ARP Request, ignoring. */
if (memeqzero(arp->arp_spa, sizeof(struct in_addr)) == 0)
return false; /* not ARP Probe, ignoring. */
if (memcmp(arp->arp_tpa, &acd->address, sizeof(struct in_addr)) != 0)
return false; /* target IP address does not match, BPF code is broken? */
if (acd->check_mac_callback &&
acd->check_mac_callback(acd, (const struct ether_addr*) arp->arp_sha, acd->check_mac_userdata) > 0)
/* sender hardware is one of the host's interfaces, ignoring. */
return true;
return true; /* conflict! */
}
static void ipv4acd_on_conflict(sd_ipv4acd *acd) {
assert(acd);
acd->n_conflict++;
(void) in_addr_to_string(AF_INET, &addr, &address);
log_ipv4acd(acd, "Conflict on %s (%u)", strna(address), acd->n_conflict);
log_ipv4acd(acd, "Conflict on "IPV4_ADDRESS_FMT_STR" (%u)", IPV4_ADDRESS_FMT_VAL(acd->address), acd->n_conflict);
ipv4acd_reset(acd);
ipv4acd_client_notify(acd, SD_IPV4ACD_EVENT_CONFLICT);
@ -367,7 +382,7 @@ static int ipv4acd_on_packet(
case IPV4ACD_STATE_ANNOUNCING:
case IPV4ACD_STATE_RUNNING:
if (ipv4acd_arp_conflict(acd, &packet)) {
if (ipv4acd_arp_conflict(acd, &packet, true)) {
usec_t ts;
assert_se(sd_event_now(acd->event, clock_boottime_or_monotonic(), &ts) >= 0);
@ -375,12 +390,13 @@ static int ipv4acd_on_packet(
/* Defend address */
if (ts > acd->defend_window) {
acd->defend_window = ts + DEFEND_INTERVAL_USEC;
r = arp_send_announcement(acd->fd, acd->ifindex, acd->address, &acd->mac_addr);
r = arp_send_announcement(acd->fd, acd->ifindex, &acd->address, &acd->mac_addr);
if (r < 0) {
log_ipv4acd_errno(acd, r, "Failed to send ARP announcement: %m");
goto fail;
} else
log_ipv4acd(acd, "DEFEND");
}
log_ipv4acd(acd, "Defending "IPV4_ADDRESS_FMT_STR, IPV4_ADDRESS_FMT_VAL(acd->address));
} else
ipv4acd_on_conflict(acd);
@ -390,8 +406,8 @@ static int ipv4acd_on_packet(
case IPV4ACD_STATE_WAITING_PROBE:
case IPV4ACD_STATE_PROBING:
case IPV4ACD_STATE_WAITING_ANNOUNCE:
/* BPF ensures this packet indicates a conflict */
ipv4acd_on_conflict(acd);
if (ipv4acd_arp_conflict(acd, &packet, false))
ipv4acd_on_conflict(acd);
break;
default:
@ -440,12 +456,24 @@ const char *sd_ipv4acd_get_ifname(sd_ipv4acd *acd) {
}
int sd_ipv4acd_set_mac(sd_ipv4acd *acd, const struct ether_addr *addr) {
int r;
assert_return(acd, -EINVAL);
assert_return(addr, -EINVAL);
assert_return(acd->state == IPV4ACD_STATE_INIT, -EBUSY);
assert_return(!ether_addr_is_null(addr), -EINVAL);
acd->mac_addr = *addr;
if (!sd_ipv4acd_is_running(acd))
return 0;
assert(acd->fd >= 0);
r = arp_update_filter(acd->fd, &acd->address, &acd->mac_addr);
if (r < 0) {
ipv4acd_reset(acd);
return r;
}
return 0;
}
@ -485,21 +513,51 @@ int sd_ipv4acd_set_callback(sd_ipv4acd *acd, sd_ipv4acd_callback_t cb, void *use
return 0;
}
int sd_ipv4acd_set_check_mac_callback(sd_ipv4acd *acd, sd_ipv4acd_check_mac_callback_t cb, void *userdata) {
assert_return(acd, -EINVAL);
acd->check_mac_callback = cb;
acd->check_mac_userdata = userdata;
return 0;
}
int sd_ipv4acd_set_address(sd_ipv4acd *acd, const struct in_addr *address) {
int r;
assert_return(acd, -EINVAL);
assert_return(address, -EINVAL);
assert_return(acd->state == IPV4ACD_STATE_INIT, -EBUSY);
assert_return(in4_addr_is_set(address), -EINVAL);
acd->address = address->s_addr;
if (in4_addr_equal(&acd->address, address))
return 0;
acd->address = *address;
if (!sd_ipv4acd_is_running(acd))
return 0;
assert(acd->fd >= 0);
r = arp_update_filter(acd->fd, &acd->address, &acd->mac_addr);
if (r < 0)
goto fail;
r = ipv4acd_set_next_wakeup(acd, 0, 0);
if (r < 0)
goto fail;
ipv4acd_set_state(acd, IPV4ACD_STATE_STARTED, true);
return 0;
fail:
ipv4acd_reset(acd);
return r;
}
int sd_ipv4acd_get_address(sd_ipv4acd *acd, struct in_addr *address) {
assert_return(acd, -EINVAL);
assert_return(address, -EINVAL);
address->s_addr = acd->address;
*address = acd->address;
return 0;
}
@ -516,16 +574,15 @@ int sd_ipv4acd_start(sd_ipv4acd *acd, bool reset_conflicts) {
assert_return(acd, -EINVAL);
assert_return(acd->event, -EINVAL);
assert_return(acd->ifindex > 0, -EINVAL);
assert_return(acd->address != 0, -EINVAL);
assert_return(in4_addr_is_set(&acd->address), -EINVAL);
assert_return(!ether_addr_is_null(&acd->mac_addr), -EINVAL);
assert_return(acd->state == IPV4ACD_STATE_INIT, -EBUSY);
r = arp_network_bind_raw_socket(acd->ifindex, acd->address, &acd->mac_addr);
r = arp_network_bind_raw_socket(acd->ifindex, &acd->address, &acd->mac_addr);
if (r < 0)
return r;
CLOSE_AND_REPLACE(acd->fd, r);
acd->defend_window = 0;
if (reset_conflicts)
acd->n_conflict = 0;

View file

@ -48,7 +48,10 @@ struct sd_ipv4ll {
be32_t claimed_address;
sd_ipv4ll_callback_t callback;
void* userdata;
void *userdata;
sd_ipv4ll_check_mac_callback_t check_mac_callback;
void *check_mac_userdata;
};
#define log_ipv4ll_errno(ll, error, fmt, ...) \
@ -62,7 +65,8 @@ struct sd_ipv4ll {
sd_ipv4ll_get_ifname(ll), \
0, fmt, ##__VA_ARGS__)
static void ipv4ll_on_acd(sd_ipv4acd *ll, int event, void *userdata);
static void ipv4ll_on_acd(sd_ipv4acd *acd, int event, void *userdata);
static int ipv4ll_check_mac(sd_ipv4acd *acd, const struct ether_addr *mac, void *userdata);
static sd_ipv4ll *ipv4ll_free(sd_ipv4ll *ll) {
assert(ll);
@ -93,6 +97,10 @@ int sd_ipv4ll_new(sd_ipv4ll **ret) {
if (r < 0)
return r;
r = sd_ipv4acd_set_check_mac_callback(ll->acd, ipv4ll_check_mac, ll);
if (r < 0)
return r;
*ret = TAKE_PTR(ll);
return 0;
@ -139,7 +147,7 @@ int sd_ipv4ll_set_mac(sd_ipv4ll *ll, const struct ether_addr *addr) {
assert_return(ll, -EINVAL);
assert_return(addr, -EINVAL);
assert_return(sd_ipv4ll_is_running(ll) == 0, -EBUSY);
assert_return(!ether_addr_is_null(addr), -EINVAL);
r = sd_ipv4acd_set_mac(ll->acd, addr);
if (r < 0)
@ -170,6 +178,15 @@ int sd_ipv4ll_set_callback(sd_ipv4ll *ll, sd_ipv4ll_callback_t cb, void *userdat
return 0;
}
int sd_ipv4ll_set_check_mac_callback(sd_ipv4ll *ll, sd_ipv4ll_check_mac_callback_t cb, void *userdata) {
assert_return(ll, -EINVAL);
ll->check_mac_callback = cb;
ll->check_mac_userdata = userdata;
return 0;
}
int sd_ipv4ll_get_address(sd_ipv4ll *ll, struct in_addr *address) {
assert_return(ll, -EINVAL);
assert_return(address, -EINVAL);
@ -353,3 +370,14 @@ void ipv4ll_on_acd(sd_ipv4acd *acd, int event, void *userdata) {
error:
ipv4ll_client_notify(ll, SD_IPV4LL_EVENT_STOP);
}
static int ipv4ll_check_mac(sd_ipv4acd *acd, const struct ether_addr *mac, void *userdata) {
sd_ipv4ll *ll = userdata;
assert(ll);
if (ll->check_mac_callback)
return ll->check_mac_callback(ll, mac, ll->check_mac_userdata);
return 0;
}

View file

@ -87,6 +87,11 @@ DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(event_source_type, int);
SOURCE_DEFER, \
SOURCE_INOTIFY)
/* This is used to assert that we didn't pass an unexpected source type to event_source_time_prioq_put().
* Time sources and ratelimited sources can be passed, so effectively this is the same as the
* EVENT_SOURCE_CAN_RATE_LIMIT() macro. */
#define EVENT_SOURCE_USES_TIME_PRIOQ(t) EVENT_SOURCE_CAN_RATE_LIMIT(t)
struct sd_event {
unsigned n_ref;
@ -1200,6 +1205,7 @@ static int event_source_time_prioq_put(
assert(s);
assert(d);
assert(EVENT_SOURCE_USES_TIME_PRIOQ(s->type));
r = prioq_put(d->earliest, s, &s->earliest_index);
if (r < 0)
@ -2978,6 +2984,7 @@ static int event_arm_timer(
d->needs_rearm = false;
a = prioq_peek(d->earliest);
assert(!a || EVENT_SOURCE_USES_TIME_PRIOQ(a->type));
if (!a || a->enabled == SD_EVENT_OFF || time_event_source_next(a) == USEC_INFINITY) {
if (d->fd < 0)
@ -2995,7 +3002,8 @@ static int event_arm_timer(
}
b = prioq_peek(d->latest);
assert_se(b && b->enabled != SD_EVENT_OFF);
assert(!b || EVENT_SOURCE_USES_TIME_PRIOQ(b->type));
assert(b && b->enabled != SD_EVENT_OFF);
t = sleep_between(e, time_event_source_next(a), time_event_source_latest(b));
if (d->next == t)
@ -3075,6 +3083,8 @@ static int process_timer(
for (;;) {
s = prioq_peek(d->earliest);
assert(!s || EVENT_SOURCE_USES_TIME_PRIOQ(s->type));
if (!s || time_event_source_next(s) > n)
break;
@ -3635,6 +3645,8 @@ static int dispatch_exit(sd_event *e) {
assert(e);
p = prioq_peek(e->exit);
assert(!p || p->type == SOURCE_EXIT);
if (!p || event_source_is_offline(p)) {
e->state = SD_EVENT_FINISHED;
return 0;

View file

@ -6,6 +6,11 @@
#include <fcntl.h>
#include <unistd.h>
#if HAVE_OPENSSL
#include <openssl/hmac.h>
#include <openssl/sha.h>
#endif
#include "sd-id128.h"
#include "alloc-util.h"
@ -13,7 +18,9 @@
#include "hexdecoct.h"
#include "id128-util.h"
#include "io-util.h"
#if !HAVE_OPENSSL
#include "khash.h"
#endif
#include "macro.h"
#include "missing_syscall.h"
#include "random-util.h"
@ -276,13 +283,28 @@ _public_ int sd_id128_randomize(sd_id128_t *ret) {
}
static int get_app_specific(sd_id128_t base, sd_id128_t app_id, sd_id128_t *ret) {
_cleanup_(khash_unrefp) khash *h = NULL;
sd_id128_t result;
const void *p;
int r;
assert(ret);
#if HAVE_OPENSSL
/* We prefer doing this in-process, since we this means we are not dependent on kernel configuration,
* and this also works in locked down container environments. But some distros don't like OpenSSL's
* license and its (in-) compatibility with GPL2, hence also support khash */
uint8_t md[256/8];
if (!HMAC(EVP_sha256(),
&base, sizeof(base),
(const unsigned char*) &app_id, sizeof(app_id),
md, NULL))
return -ENOTRECOVERABLE;
/* Take only the first half. */
memcpy(&result, md, MIN(sizeof(md), sizeof(result)));
#else
_cleanup_(khash_unrefp) khash *h = NULL;
const void *p;
int r;
r = khash_new_with_key(&h, "hmac(sha256)", &base, sizeof(base));
if (r < 0)
return r;
@ -297,6 +319,7 @@ static int get_app_specific(sd_id128_t base, sd_id128_t app_id, sd_id128_t *ret)
/* We chop off the trailing 16 bytes */
memcpy(&result, p, MIN(khash_get_size(h), sizeof(result)));
#endif
*ret = id128_make_v4_uuid(result);
return 0;

View file

@ -36,11 +36,13 @@ enum {
typedef struct sd_ipv4acd sd_ipv4acd;
typedef void (*sd_ipv4acd_callback_t)(sd_ipv4acd *acd, int event, void *userdata);
typedef int (*sd_ipv4acd_check_mac_callback_t)(sd_ipv4acd *acd, const struct ether_addr *mac, void *userdata);
int sd_ipv4acd_detach_event(sd_ipv4acd *acd);
int sd_ipv4acd_attach_event(sd_ipv4acd *acd, sd_event *event, int64_t priority);
int sd_ipv4acd_get_address(sd_ipv4acd *acd, struct in_addr *address);
int sd_ipv4acd_set_callback(sd_ipv4acd *acd, sd_ipv4acd_callback_t cb, void *userdata);
int sd_ipv4acd_set_check_mac_callback(sd_ipv4acd *acd, sd_ipv4acd_check_mac_callback_t cb, void *userdata);
int sd_ipv4acd_set_mac(sd_ipv4acd *acd, const struct ether_addr *addr);
int sd_ipv4acd_set_ifindex(sd_ipv4acd *acd, int interface_index);
int sd_ipv4acd_get_ifindex(sd_ipv4acd *acd);

View file

@ -36,11 +36,13 @@ enum {
typedef struct sd_ipv4ll sd_ipv4ll;
typedef void (*sd_ipv4ll_callback_t)(sd_ipv4ll *ll, int event, void *userdata);
typedef int (*sd_ipv4ll_check_mac_callback_t)(sd_ipv4ll *ll, const struct ether_addr *mac, 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_check_mac_callback(sd_ipv4ll *ll, sd_ipv4ll_check_mac_callback_t cb, void *userdata);
int sd_ipv4ll_set_mac(sd_ipv4ll *ll, const struct ether_addr *addr);
int sd_ipv4ll_set_ifindex(sd_ipv4ll *ll, int interface_index);
int sd_ipv4ll_get_ifindex(sd_ipv4ll *ll);

View file

@ -10,6 +10,7 @@
#include "escape.h"
#include "hexdecoct.h"
#include "macro.h"
#include "strv.h"
#include "utf8.h"
int cescape_char(char c, char *buf) {
@ -291,10 +292,12 @@ int cunescape_one(const char *p, size_t length, char32_t *ret, bool *eight_bit,
}
#if 0 /* NM_IGNORED */
int cunescape_length_with_prefix(const char *s, size_t length, const char *prefix, UnescapeFlags flags, char **ret) {
char *r, *t;
ssize_t cunescape_length_with_prefix(const char *s, size_t length, const char *prefix, UnescapeFlags flags, char **ret) {
_cleanup_free_ char *ans = NULL;
char *t;
const char *f;
size_t pl;
int r;
assert(s);
assert(ret);
@ -303,18 +306,17 @@ int cunescape_length_with_prefix(const char *s, size_t length, const char *prefi
pl = strlen_ptr(prefix);
r = new(char, pl+length+1);
if (!r)
ans = new(char, pl+length+1);
if (!ans)
return -ENOMEM;
if (prefix)
memcpy(r, prefix, pl);
memcpy(ans, prefix, pl);
for (f = s, t = r + pl; f < s + length; f++) {
for (f = s, t = ans + pl; f < s + length; f++) {
size_t remaining;
bool eight_bit = false;
char32_t u;
int k;
remaining = s + length - f;
assert(remaining > 0);
@ -332,23 +334,21 @@ int cunescape_length_with_prefix(const char *s, size_t length, const char *prefi
continue;
}
free(r);
return -EINVAL;
}
k = cunescape_one(f + 1, remaining - 1, &u, &eight_bit, flags & UNESCAPE_ACCEPT_NUL);
if (k < 0) {
r = cunescape_one(f + 1, remaining - 1, &u, &eight_bit, flags & UNESCAPE_ACCEPT_NUL);
if (r < 0) {
if (flags & UNESCAPE_RELAX) {
/* Invalid escape code, let's take it literal then */
*(t++) = '\\';
continue;
}
free(r);
return k;
return r;
}
f += k;
f += r;
if (eight_bit)
/* One byte? Set directly as specified */
*(t++) = u;
@ -359,8 +359,9 @@ int cunescape_length_with_prefix(const char *s, size_t length, const char *prefi
*t = 0;
*ret = r;
return t - r;
assert(t >= ans); /* Let static analyzers know that the answer is non-negative. */
*ret = TAKE_PTR(ans);
return t - *ret;
}
char* xescape_full(const char *s, const char *bad, size_t console_width, XEscapeFlags flags) {
@ -445,7 +446,6 @@ char* escape_non_printable_full(const char *str, size_t console_width, XEscapeFl
console_width,
FLAGS_SET(flags, XESCAPE_FORCE_ELLIPSIS));
}
#endif /* NM_IGNORED */
char* octescape(const char *s, size_t len) {
char *r, *t;
@ -546,3 +546,24 @@ char* shell_maybe_quote(const char *s, ShellEscapeFlags flags) {
return str_realloc(buf);
}
char* quote_command_line(char **argv) {
_cleanup_free_ char *result = NULL;
assert(argv);
char **a;
STRV_FOREACH(a, argv) {
_cleanup_free_ char *t = NULL;
t = shell_maybe_quote(*a, SHELL_ESCAPE_EMPTY);
if (!t)
return NULL;
if (!strextend_with_separator(&result, " ", t))
return NULL;
}
return TAKE_PTR(result);
}
#endif /* NM_IGNORED */

View file

@ -45,14 +45,15 @@ char* cescape(const char *s);
char* cescape_length(const char *s, size_t n);
int cescape_char(char c, char *buf);
int cunescape_length_with_prefix(const char *s, size_t length, const char *prefix, UnescapeFlags flags, char **ret);
static inline int cunescape_length(const char *s, size_t length, UnescapeFlags flags, char **ret) {
int cunescape_one(const char *p, size_t length, char32_t *ret, bool *eight_bit, bool accept_nul);
ssize_t cunescape_length_with_prefix(const char *s, size_t length, const char *prefix, UnescapeFlags flags, char **ret);
static inline ssize_t cunescape_length(const char *s, size_t length, UnescapeFlags flags, char **ret) {
return cunescape_length_with_prefix(s, length, NULL, flags, ret);
}
static inline int cunescape(const char *s, UnescapeFlags flags, char **ret) {
static inline ssize_t cunescape(const char *s, UnescapeFlags flags, char **ret) {
return cunescape_length(s, strlen(s), flags, ret);
}
int cunescape_one(const char *p, size_t length, char32_t *ret, bool *eight_bit, bool accept_nul);
typedef enum XEscapeFlags {
XESCAPE_8_BIT = 1 << 0,
@ -68,3 +69,4 @@ char* escape_non_printable_full(const char *str, size_t console_width, XEscapeFl
char* shell_escape(const char *s, const char *bad);
char* shell_maybe_quote(const char *s, ShellEscapeFlags flags);
char* quote_command_line(char **argv);

View file

@ -39,6 +39,16 @@ int hw_addr_compare(const struct hw_addr_data *a, const struct hw_addr_data *b)
return memcmp(a->bytes, b->bytes, a->length);
}
static void hw_addr_hash_func(const struct hw_addr_data *p, struct siphash *state) {
assert(p);
assert(state);
siphash24_compress(&p->length, sizeof(p->length), state);
siphash24_compress(p->bytes, p->length, state);
}
DEFINE_HASH_OPS(hw_addr_hash_ops, struct hw_addr_data, hw_addr_hash_func, hw_addr_compare);
char* ether_addr_to_string(const struct ether_addr *addr, char buffer[ETHER_ADDR_TO_STRING_MAX]) {
assert(addr);
assert(buffer);

View file

@ -23,7 +23,9 @@ struct hw_addr_data {
#define HW_ADDR_TO_STRING_MAX (3*HW_ADDR_MAX_SIZE)
char* hw_addr_to_string(const struct hw_addr_data *addr, char buffer[HW_ADDR_TO_STRING_MAX]);
/* Use only as function argument, never stand-alone! */
/* Note: the lifetime of the compound literal is the immediately surrounding block,
* see C11 §6.5.2.5, and
* https://stackoverflow.com/questions/34880638/compound-literal-lifetime-and-if-blocks */
#define HW_ADDR_TO_STR(hw_addr) hw_addr_to_string((hw_addr), (char[HW_ADDR_TO_STRING_MAX]){})
#define HW_ADDR_NULL ((const struct hw_addr_data){})
@ -36,6 +38,8 @@ static inline bool hw_addr_is_null(const struct hw_addr_data *addr) {
return hw_addr_equal(addr, &HW_ADDR_NULL);
}
extern const struct hash_ops hw_addr_hash_ops;
#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]

View file

@ -29,6 +29,7 @@ int extract_first_word(const char **p, char **ret, const char *separators, Extra
assert(p);
assert(ret);
assert(!FLAGS_SET(flags, EXTRACT_KEEP_QUOTE | EXTRACT_UNQUOTE));
/* Bail early if called after last value or with no input */
if (!*p)
@ -52,7 +53,8 @@ int extract_first_word(const char **p, char **ret, const char *separators, Extra
goto finish_force_terminate;
else if (strchr(separators, c)) {
if (flags & EXTRACT_DONT_COALESCE_SEPARATORS) {
(*p)++;
if (!(flags & EXTRACT_RETAIN_SEPARATORS))
(*p)++;
goto finish_force_next;
}
} else {
@ -125,48 +127,58 @@ int extract_first_word(const char **p, char **ret, const char *separators, Extra
return -EINVAL;
} else if (c == quote) { /* found the end quote */
quote = 0;
break;
if (flags & EXTRACT_UNQUOTE)
break;
} else if (c == '\\' && !(flags & EXTRACT_RETAIN_ESCAPE)) {
backslash = true;
break;
} else {
if (!GREEDY_REALLOC(s, sz+2))
return -ENOMEM;
s[sz++] = c;
}
if (!GREEDY_REALLOC(s, sz+2))
return -ENOMEM;
s[sz++] = c;
if (quote == 0)
break;
}
} else {
for (;; (*p)++, c = **p) {
if (c == 0)
goto finish_force_terminate;
else if (IN_SET(c, '\'', '"') && (flags & EXTRACT_UNQUOTE)) {
else if (IN_SET(c, '\'', '"') && (flags & (EXTRACT_KEEP_QUOTE | EXTRACT_UNQUOTE))) {
quote = c;
break;
if (flags & EXTRACT_UNQUOTE)
break;
} else if (c == '\\' && !(flags & EXTRACT_RETAIN_ESCAPE)) {
backslash = true;
break;
} else if (strchr(separators, c)) {
if (flags & EXTRACT_DONT_COALESCE_SEPARATORS) {
(*p)++;
if (!(flags & EXTRACT_RETAIN_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;
}
if (!(flags & EXTRACT_RETAIN_SEPARATORS))
/* 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, sz+2))
return -ENOMEM;
s[sz++] = c;
}
if (!GREEDY_REALLOC(s, sz+2))
return -ENOMEM;
s[sz++] = c;
if (quote != 0)
break;
}
}
}

View file

@ -8,9 +8,11 @@ typedef enum ExtractFlags {
EXTRACT_CUNESCAPE = 1 << 1, /* Unescape known escape sequences. */
EXTRACT_UNESCAPE_RELAX = 1 << 2, /* Allow and keep unknown escape sequences, allow and keep trailing backslash. */
EXTRACT_UNESCAPE_SEPARATORS = 1 << 3, /* Unescape separators (those specified, or whitespace by default). */
EXTRACT_UNQUOTE = 1 << 4, /* Remove quoting with "" and ''. */
EXTRACT_DONT_COALESCE_SEPARATORS = 1 << 5, /* Don't treat multiple adjacent separators as one */
EXTRACT_RETAIN_ESCAPE = 1 << 6, /* Treat escape character '\' as any other character without special meaning */
EXTRACT_KEEP_QUOTE = 1 << 4, /* Ignore separators in quoting with "" and ''. */
EXTRACT_UNQUOTE = 1 << 5, /* Ignore separators in quoting with "" and '', and remove the quotes. */
EXTRACT_DONT_COALESCE_SEPARATORS = 1 << 6, /* Don't treat multiple adjacent separators as one */
EXTRACT_RETAIN_ESCAPE = 1 << 7, /* Treat escape character '\' as any other character without special meaning */
EXTRACT_RETAIN_SEPARATORS = 1 << 8, /* Do not advance the original string pointer past the separator(s) */
/* Note that if no flags are specified, escaped escape characters will be silently stripped. */
} ExtractFlags;

View file

@ -4,19 +4,20 @@
#include <errno.h>
#include <fcntl.h>
#include <linux/btrfs.h>
#include <linux/magic.h>
#include <sys/ioctl.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <unistd.h>
#include "alloc-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 "memfd-util.h"
#include "missing_fcntl.h"
#include "missing_syscall.h"
#include "parse-util.h"
@ -210,10 +211,9 @@ static int get_max_fd(void) {
return (int) (m - 1);
}
int close_all_fds(const int except[], size_t n_except) {
int close_all_fds_full(int except[], size_t n_except, bool allow_alloc) {
static bool have_close_range = true; /* Assume we live in the future */
_cleanup_closedir_ DIR *d = NULL;
struct dirent *de;
int r = 0;
assert(n_except == 0 || except);
@ -229,129 +229,104 @@ int close_all_fds(const int except[], size_t n_except) {
/* Close everything. Yay! */
if (close_range(3, -1, 0) >= 0)
return 1;
return 0;
if (!ERRNO_IS_NOT_SUPPORTED(errno) && !ERRNO_IS_PRIVILEGE(errno))
if (ERRNO_IS_NOT_SUPPORTED(errno) || ERRNO_IS_PRIVILEGE(errno))
have_close_range = false;
else
return -errno;
have_close_range = false;
} else {
_cleanup_free_ int *sorted_malloc = NULL;
size_t n_sorted;
int *sorted;
typesafe_qsort(except, n_except, cmp_int);
assert(n_except < SIZE_MAX);
n_sorted = n_except + 1;
for (size_t i = 0; i < n_except; i++) {
int start = i == 0 ? 2 : MAX(except[i-1], 2); /* The first three fds shall always remain open */
int end = MAX(except[i], 2);
if (n_sorted > 64) /* Use heap for large numbers of fds, stack otherwise */
sorted = sorted_malloc = new(int, n_sorted);
else
sorted = newa(int, n_sorted);
assert(end >= start);
if (sorted) {
int c = 0;
memcpy(sorted, except, n_except * sizeof(int));
/* Let's add fd 2 to the list of fds, to simplify the loop below, as this
* allows us to cover the head of the array the same way as the body */
sorted[n_sorted-1] = 2;
typesafe_qsort(sorted, n_sorted, cmp_int);
for (size_t i = 0; i < n_sorted-1; i++) {
int start, end;
start = MAX(sorted[i], 2); /* The first three fds shall always remain open */
end = MAX(sorted[i+1], 2);
assert(end >= start);
if (end - start <= 1)
continue;
/* Close everything between the start and end fds (both of which shall stay open) */
if (close_range(start + 1, end - 1, 0) < 0) {
if (!ERRNO_IS_NOT_SUPPORTED(errno) && !ERRNO_IS_PRIVILEGE(errno))
return -errno;
if (end - start <= 1)
continue;
/* Close everything between the start and end fds (both of which shall stay open) */
if (close_range(start + 1, end - 1, 0) < 0) {
if (ERRNO_IS_NOT_SUPPORTED(errno) || ERRNO_IS_PRIVILEGE(errno))
have_close_range = false;
break;
}
c += end - start - 1;
}
if (have_close_range) {
/* The loop succeeded. Let's now close everything beyond the end */
if (sorted[n_sorted-1] >= INT_MAX) /* Dont let the addition below overflow */
return c;
if (close_range(sorted[n_sorted-1] + 1, -1, 0) >= 0)
return c + 1;
if (!ERRNO_IS_NOT_SUPPORTED(errno) && !ERRNO_IS_PRIVILEGE(errno))
else
return -errno;
have_close_range = false;
goto opendir_fallback;
}
}
}
/* Fallback on OOM or if close_range() is not supported */
/* The loop succeeded. Let's now close everything beyond the end */
if (except[n_except-1] >= INT_MAX) /* Don't let the addition below overflow */
return 0;
int start = MAX(except[n_except-1], 2);
if (close_range(start + 1, -1, 0) >= 0)
return 0;
if (ERRNO_IS_NOT_SUPPORTED(errno) || ERRNO_IS_PRIVILEGE(errno))
have_close_range = false;
else
return -errno;
}
}
d = opendir("/proc/self/fd");
if (!d) {
int fd, max_fd;
/* Fallback for when close_range() is not supported */
opendir_fallback:
d = allow_alloc ? opendir("/proc/self/fd") : NULL;
if (d) {
struct dirent *de;
/* When /proc isn't available (for example in chroots) the fallback is brute forcing through
* the fd table */
FOREACH_DIRENT(de, d, return -errno) {
int fd = -1, q;
max_fd = get_max_fd();
if (max_fd < 0)
return max_fd;
if (safe_atoi(de->d_name, &fd) < 0)
/* Let's better ignore this, just in case */
continue;
/* Refuse to do the loop over more too many elements. It's better to fail immediately than to
* spin the CPU for a long time. */
if (max_fd > MAX_FD_LOOP_LIMIT)
return log_debug_errno(SYNTHETIC_ERRNO(EPERM),
"/proc/self/fd is inaccessible. Refusing to loop over %d potential fds.",
max_fd);
if (fd < 3)
continue;
for (fd = 3; fd >= 0; fd = fd < max_fd ? fd + 1 : -1) {
int q;
if (fd == dirfd(d))
continue;
if (fd_in_set(fd, except, n_except))
continue;
q = close_nointr(fd);
if (q < 0 && q != -EBADF && r >= 0)
if (q < 0 && q != -EBADF && r >= 0) /* Valgrind has its own FD and doesn't want to have it closed */
r = q;
}
return r;
}
FOREACH_DIRENT(de, d, return -errno) {
int fd = -1, q;
/* Fallback for when /proc isn't available (for example in chroots) or when we cannot allocate by
* brute-forcing through the file descriptor table. */
if (safe_atoi(de->d_name, &fd) < 0)
/* Let's better ignore this, just in case */
continue;
int max_fd = get_max_fd();
if (max_fd < 0)
return max_fd;
if (fd < 3)
continue;
/* Refuse to do the loop over more too many elements. It's better to fail immediately than to
* spin the CPU for a long time. */
if (max_fd > MAX_FD_LOOP_LIMIT)
return log_debug_errno(SYNTHETIC_ERRNO(EPERM),
"/proc/self/fd is inaccessible. Refusing to loop over %d potential fds.",
max_fd);
if (fd == dirfd(d))
continue;
for (int fd = 3; fd >= 0; fd = fd < max_fd ? fd + 1 : -1) {
int q;
if (fd_in_set(fd, except, n_except))
continue;
q = close_nointr(fd);
if (q < 0 && q != -EBADF && r >= 0) /* Valgrind has its own FD and doesn't want to have it closed */
if (q < 0 && q != -EBADF && r >= 0)
r = q;
}
@ -524,343 +499,6 @@ int move_fd(int from, int to, int cloexec) {
return to;
}
int acquire_data_fd(const void *data, size_t size, unsigned flags) {
_cleanup_close_pair_ int pipefds[2] = { -1, -1 };
char pattern[] = "/dev/shm/data-fd-XXXXXX";
_cleanup_close_ int fd = -1;
int isz = 0, r;
ssize_t n;
off_t f;
assert(data || size == 0);
/* Acquire a read-only file descriptor that when read from returns the specified data. This is much more
* complex than I wish it was. But here's why:
*
* a) First we try to use memfds. They are the best option, as we can seal them nicely to make them
* read-only. Unfortunately they require kernel 3.17, and at the time of writing we still support 3.14.
*
* b) Then, we try classic pipes. They are the second best options, as we can close the writing side, retaining
* a nicely read-only fd in the reading side. However, they are by default quite small, and unprivileged
* clients can only bump their size to a system-wide limit, which might be quite low.
*
* c) Then, we try an O_TMPFILE file in /dev/shm (that dir is the only suitable one known to exist from
* earliest boot on). To make it read-only we open the fd a second time with O_RDONLY via
* /proc/self/<fd>. Unfortunately O_TMPFILE is not available on older kernels on tmpfs.
*
* d) Finally, we try creating a regular file in /dev/shm, which we then delete.
*
* It sucks a bit that depending on the situation we return very different objects here, but that's Linux I
* figure. */
if (size == 0 && ((flags & ACQUIRE_NO_DEV_NULL) == 0)) {
/* As a special case, return /dev/null if we have been called for an empty data block */
r = open("/dev/null", O_RDONLY|O_CLOEXEC|O_NOCTTY);
if (r < 0)
return -errno;
return r;
}
if ((flags & ACQUIRE_NO_MEMFD) == 0) {
fd = memfd_new("data-fd");
if (fd < 0)
goto try_pipe;
n = write(fd, data, size);
if (n < 0)
return -errno;
if ((size_t) n != size)
return -EIO;
f = lseek(fd, 0, SEEK_SET);
if (f != 0)
return -errno;
r = memfd_set_sealed(fd);
if (r < 0)
return r;
return TAKE_FD(fd);
}
try_pipe:
if ((flags & ACQUIRE_NO_PIPE) == 0) {
if (pipe2(pipefds, O_CLOEXEC|O_NONBLOCK) < 0)
return -errno;
isz = fcntl(pipefds[1], F_GETPIPE_SZ, 0);
if (isz < 0)
return -errno;
if ((size_t) isz < size) {
isz = (int) size;
if (isz < 0 || (size_t) isz != size)
return -E2BIG;
/* Try to bump the pipe size */
(void) fcntl(pipefds[1], F_SETPIPE_SZ, isz);
/* See if that worked */
isz = fcntl(pipefds[1], F_GETPIPE_SZ, 0);
if (isz < 0)
return -errno;
if ((size_t) isz < size)
goto try_dev_shm;
}
n = write(pipefds[1], data, size);
if (n < 0)
return -errno;
if ((size_t) n != size)
return -EIO;
(void) fd_nonblock(pipefds[0], false);
return TAKE_FD(pipefds[0]);
}
try_dev_shm:
if ((flags & ACQUIRE_NO_TMPFILE) == 0) {
fd = open("/dev/shm", O_RDWR|O_TMPFILE|O_CLOEXEC, 0500);
if (fd < 0)
goto try_dev_shm_without_o_tmpfile;
n = write(fd, data, size);
if (n < 0)
return -errno;
if ((size_t) n != size)
return -EIO;
/* Let's reopen the thing, in order to get an O_RDONLY fd for the original O_RDWR one */
return fd_reopen(fd, O_RDONLY|O_CLOEXEC);
}
try_dev_shm_without_o_tmpfile:
if ((flags & ACQUIRE_NO_REGULAR) == 0) {
fd = mkostemp_safe(pattern);
if (fd < 0)
return fd;
n = write(fd, data, size);
if (n < 0) {
r = -errno;
goto unlink_and_return;
}
if ((size_t) n != size) {
r = -EIO;
goto unlink_and_return;
}
/* Let's reopen the thing, in order to get an O_RDONLY fd for the original O_RDWR one */
r = open(pattern, O_RDONLY|O_CLOEXEC);
if (r < 0)
r = -errno;
unlink_and_return:
(void) unlink(pattern);
return r;
}
return -EOPNOTSUPP;
}
/* When the data is smaller or equal to 64K, try to place the copy in a memfd/pipe */
#define DATA_FD_MEMORY_LIMIT (64U*1024U)
/* If memfd/pipe didn't work out, then let's use a file in /tmp up to a size of 1M. If it's large than that use /var/tmp instead. */
#define DATA_FD_TMP_LIMIT (1024U*1024U)
int fd_duplicate_data_fd(int fd) {
_cleanup_close_ int copy_fd = -1, tmp_fd = -1;
_cleanup_free_ void *remains = NULL;
size_t remains_size = 0;
const char *td;
struct stat st;
int r;
/* Creates a 'data' fd from the specified source fd, containing all the same data in a read-only fashion, but
* independent of it (i.e. the source fd can be closed and unmounted after this call succeeded). Tries to be
* somewhat smart about where to place the data. In the best case uses a memfd(). If memfd() are not supported
* uses a pipe instead. For larger data will use an unlinked file in /tmp, and for even larger data one in
* /var/tmp. */
if (fstat(fd, &st) < 0)
return -errno;
/* For now, let's only accept regular files, sockets, pipes and char devices */
if (S_ISDIR(st.st_mode))
return -EISDIR;
if (S_ISLNK(st.st_mode))
return -ELOOP;
if (!S_ISREG(st.st_mode) && !S_ISSOCK(st.st_mode) && !S_ISFIFO(st.st_mode) && !S_ISCHR(st.st_mode))
return -EBADFD;
/* If we have reason to believe the data is bounded in size, then let's use memfds or pipes as backing fd. Note
* that we use the reported regular file size only as a hint, given that there are plenty special files in
* /proc and /sys which report a zero file size but can be read from. */
if (!S_ISREG(st.st_mode) || st.st_size < DATA_FD_MEMORY_LIMIT) {
/* Try a memfd first */
copy_fd = memfd_new("data-fd");
if (copy_fd >= 0) {
off_t f;
r = copy_bytes(fd, copy_fd, DATA_FD_MEMORY_LIMIT, 0);
if (r < 0)
return r;
f = lseek(copy_fd, 0, SEEK_SET);
if (f != 0)
return -errno;
if (r == 0) {
/* Did it fit into the limit? If so, we are done. */
r = memfd_set_sealed(copy_fd);
if (r < 0)
return r;
return TAKE_FD(copy_fd);
}
/* Hmm, pity, this didn't fit. Let's fall back to /tmp then, see below */
} else {
_cleanup_(close_pairp) int pipefds[2] = { -1, -1 };
int isz;
/* If memfds aren't available, use a pipe. Set O_NONBLOCK so that we will get EAGAIN rather
* then block indefinitely when we hit the pipe size limit */
if (pipe2(pipefds, O_CLOEXEC|O_NONBLOCK) < 0)
return -errno;
isz = fcntl(pipefds[1], F_GETPIPE_SZ, 0);
if (isz < 0)
return -errno;
/* Try to enlarge the pipe size if necessary */
if ((size_t) isz < DATA_FD_MEMORY_LIMIT) {
(void) fcntl(pipefds[1], F_SETPIPE_SZ, DATA_FD_MEMORY_LIMIT);
isz = fcntl(pipefds[1], F_GETPIPE_SZ, 0);
if (isz < 0)
return -errno;
}
if ((size_t) isz >= DATA_FD_MEMORY_LIMIT) {
r = copy_bytes_full(fd, pipefds[1], DATA_FD_MEMORY_LIMIT, 0, &remains, &remains_size, NULL, NULL);
if (r < 0 && r != -EAGAIN)
return r; /* If we get EAGAIN it could be because of the source or because of
* the destination fd, we can't know, as sendfile() and friends won't
* tell us. Hence, treat this as reason to fall back, just to be
* sure. */
if (r == 0) {
/* Everything fit in, yay! */
(void) fd_nonblock(pipefds[0], false);
return TAKE_FD(pipefds[0]);
}
/* Things didn't fit in. But we read data into the pipe, let's remember that, so that
* when writing the new file we incorporate this first. */
copy_fd = TAKE_FD(pipefds[0]);
}
}
}
/* If we have reason to believe this will fit fine in /tmp, then use that as first fallback. */
if ((!S_ISREG(st.st_mode) || st.st_size < DATA_FD_TMP_LIMIT) &&
(DATA_FD_MEMORY_LIMIT + remains_size) < DATA_FD_TMP_LIMIT) {
off_t f;
tmp_fd = open_tmpfile_unlinkable(NULL /* NULL as directory means /tmp */, O_RDWR|O_CLOEXEC);
if (tmp_fd < 0)
return tmp_fd;
if (copy_fd >= 0) {
/* If we tried a memfd/pipe first and it ended up being too large, then copy this into the
* temporary file first. */
r = copy_bytes(copy_fd, tmp_fd, UINT64_MAX, 0);
if (r < 0)
return r;
assert(r == 0);
}
if (remains_size > 0) {
/* If there were remaining bytes (i.e. read into memory, but not written out yet) from the
* failed copy operation, let's flush them out next. */
r = loop_write(tmp_fd, remains, remains_size, false);
if (r < 0)
return r;
}
r = copy_bytes(fd, tmp_fd, DATA_FD_TMP_LIMIT - DATA_FD_MEMORY_LIMIT - remains_size, COPY_REFLINK);
if (r < 0)
return r;
if (r == 0)
goto finish; /* Yay, it fit in */
/* It didn't fit in. Let's not forget to use what we already used */
f = lseek(tmp_fd, 0, SEEK_SET);
if (f != 0)
return -errno;
CLOSE_AND_REPLACE(copy_fd, tmp_fd);
remains = mfree(remains);
remains_size = 0;
}
/* As last fallback use /var/tmp */
r = var_tmp_dir(&td);
if (r < 0)
return r;
tmp_fd = open_tmpfile_unlinkable(td, O_RDWR|O_CLOEXEC);
if (tmp_fd < 0)
return tmp_fd;
if (copy_fd >= 0) {
/* If we tried a memfd/pipe first, or a file in /tmp, and it ended up being too large, than copy this
* into the temporary file first. */
r = copy_bytes(copy_fd, tmp_fd, UINT64_MAX, COPY_REFLINK);
if (r < 0)
return r;
assert(r == 0);
}
if (remains_size > 0) {
/* Then, copy in any read but not yet written bytes. */
r = loop_write(tmp_fd, remains, remains_size, false);
if (r < 0)
return r;
}
/* Copy in the rest */
r = copy_bytes(fd, tmp_fd, UINT64_MAX, COPY_REFLINK);
if (r < 0)
return r;
assert(r == 0);
finish:
/* Now convert the O_RDWR file descriptor into an O_RDONLY one (and as side effect seek to the beginning of the
* file again */
return fd_reopen(tmp_fd, O_RDONLY|O_CLOEXEC);
}
#endif /* NM_IGNORED */
int fd_move_above_stdio(int fd) {
@ -1064,4 +702,21 @@ int read_nr_open(void) {
/* If we fail, fall back to the hard-coded kernel limit of 1024 * 1024. */
return 1024 * 1024;
}
/* This is here because it's fd-related and is called from sd-journal code. Other btrfs-related utilities are
* in src/shared, but libsystemd must not link to libsystemd-shared, see docs/ARCHITECTURE.md. */
int btrfs_defrag_fd(int fd) {
int r;
assert(fd >= 0);
r = fd_verify_regular(fd);
if (r < 0)
return r;
if (ioctl(fd, BTRFS_IOC_DEFRAG, NULL) < 0)
return -errno;
return 0;
}
#endif /* NM_IGNORED */

View file

@ -56,7 +56,10 @@ DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(DIR*, closedir, NULL);
int fd_nonblock(int fd, bool nonblock);
int fd_cloexec(int fd, bool cloexec);
int close_all_fds(const int except[], size_t n_except);
int close_all_fds_full(int except[], size_t n_except, bool allow_alloc);
static inline int close_all_fds(int except[], size_t n_except) {
return close_all_fds_full(except, n_except, true);
}
int same_fd(int a, int b);
@ -76,10 +79,6 @@ enum {
ACQUIRE_NO_REGULAR = 1 << 4,
};
int acquire_data_fd(const void *data, size_t size, unsigned flags);
int fd_duplicate_data_fd(int fd);
int fd_move_above_stdio(int fd);
int rearrange_stdio(int original_input_fd, int original_output_fd, int original_error_fd);
@ -107,5 +106,5 @@ static inline int make_null_stdio(void) {
int fd_reopen(int fd, int flags);
int read_nr_open(void);
int btrfs_defrag_fd(int fd);

View file

@ -526,18 +526,17 @@ int read_full_stream_full(
size_t *ret_size) {
_cleanup_free_ char *buf = NULL;
size_t n, n_next, l;
size_t n, n_next = 0, l;
int fd, r;
assert(f);
assert(ret_contents);
assert(!FLAGS_SET(flags, READ_FULL_FILE_UNBASE64 | READ_FULL_FILE_UNHEX));
assert(size != SIZE_MAX || !FLAGS_SET(flags, READ_FULL_FILE_FAIL_WHEN_LARGER));
if (offset != UINT64_MAX && offset > LONG_MAX)
if (offset != UINT64_MAX && offset > LONG_MAX) /* fseek() can only deal with "long" offsets */
return -ERANGE;
n_next = size != SIZE_MAX ? size : LINE_MAX; /* Start size */
fd = fileno(f);
if (fd >= 0) { /* If the FILE* object is backed by an fd (as opposed to memory or such, see
* fmemopen()), let's optimize our buffering */
@ -547,20 +546,20 @@ int read_full_stream_full(
return -errno;
if (S_ISREG(st.st_mode)) {
if (size == SIZE_MAX) {
/* Try to start with the right file size if we shall read the file in full. Note
* that we increase the size to read here by one, so that the first read attempt
* already makes us notice the EOF. If the reported size of the file is zero, we
* avoid this logic however, since quite likely it might be a virtual file in procfs
* that all report a zero file size. */
if (st.st_size > 0 &&
(size == SIZE_MAX || FLAGS_SET(flags, READ_FULL_FILE_FAIL_WHEN_LARGER))) {
uint64_t rsize =
LESS_BY((uint64_t) st.st_size, offset == UINT64_MAX ? 0 : offset);
/* Safety check */
if (rsize > READ_FULL_BYTES_MAX)
return -E2BIG;
/* Start with the right file size. Note that we increase the size to read
* here by one, so that the first read attempt already makes us notice the
* EOF. If the reported size of the file is zero, we avoid this logic
* however, since quite likely it might be a virtual file in procfs that all
* report a zero file size. */
if (st.st_size > 0)
if (rsize < SIZE_MAX) /* overflow check */
n_next = rsize + 1;
}
@ -569,6 +568,17 @@ int read_full_stream_full(
}
}
/* If we don't know how much to read, figure it out now. If we shall read a part of the file, then
* allocate the requested size. If we shall load the full file start with LINE_MAX. Note that if
* READ_FULL_FILE_FAIL_WHEN_LARGER we consider the specified size a safety limit, and thus also start
* with LINE_MAX, under assumption the file is most likely much shorter. */
if (n_next == 0)
n_next = size != SIZE_MAX && !FLAGS_SET(flags, READ_FULL_FILE_FAIL_WHEN_LARGER) ? size : LINE_MAX;
/* Never read more than we need to determine that our own limit is hit */
if (n_next > READ_FULL_BYTES_MAX)
n_next = READ_FULL_BYTES_MAX + 1;
if (offset != UINT64_MAX && fseek(f, offset, SEEK_SET) < 0)
return -errno;
@ -577,6 +587,11 @@ int read_full_stream_full(
char *t;
size_t k;
/* If we shall fail when reading overly large data, then read exactly one byte more than the
* specified size at max, since that'll tell us if there's anymore data beyond the limit*/
if (FLAGS_SET(flags, READ_FULL_FILE_FAIL_WHEN_LARGER) && n_next > size)
n_next = size + 1;
if (flags & READ_FULL_FILE_SECURE) {
t = malloc(n_next + 1);
if (!t) {
@ -610,14 +625,18 @@ int read_full_stream_full(
if (feof(f))
break;
if (size != SIZE_MAX) { /* If we got asked to read some specific size, we already sized the buffer right, hence leave */
if (size != SIZE_MAX && !FLAGS_SET(flags, READ_FULL_FILE_FAIL_WHEN_LARGER)) { /* If we got asked to read some specific size, we already sized the buffer right, hence leave */
assert(l == size);
break;
}
assert(k > 0); /* we can't have read zero bytes because that would have been EOF */
/* Safety check */
if (FLAGS_SET(flags, READ_FULL_FILE_FAIL_WHEN_LARGER) && l > size) {
r = -E2BIG;
goto finalize;
}
if (n >= READ_FULL_BYTES_MAX) {
r = -E2BIG;
goto finalize;

View file

@ -43,6 +43,7 @@ typedef enum {
READ_FULL_FILE_UNHEX = 1 << 2, /* hex decode what we read */
READ_FULL_FILE_WARN_WORLD_READABLE = 1 << 3, /* if regular file, log at LOG_WARNING level if access mode above 0700 */
READ_FULL_FILE_CONNECT_SOCKET = 1 << 4, /* if socket inode, connect to it and read off it */
READ_FULL_FILE_FAIL_WHEN_LARGER = 1 << 5, /* fail loading if file is larger than specified size */
} ReadFullFileFlags;
int fopen_unlocked(const char *path, const char *options, FILE **ret);

View file

@ -74,16 +74,17 @@ typedef enum {
#define FORMAT_BYTES_MAX 16U
char *format_bytes_full(char *buf, size_t l, uint64_t t, FormatBytesFlag flag);
char *format_bytes_full(char *buf, size_t l, uint64_t t, FormatBytesFlag flag) _warn_unused_result_;
_warn_unused_result_
static inline char *format_bytes(char *buf, size_t l, uint64_t t) {
return format_bytes_full(buf, l, t, FORMAT_BYTES_USE_IEC | FORMAT_BYTES_BELOW_POINT | FORMAT_BYTES_TRAILING_B);
}
static inline char *format_bytes_cgroup_protection(char *buf, size_t l, uint64_t t) {
if (t == CGROUP_LIMIT_MAX) {
(void) snprintf(buf, l, "%s", "infinity");
return buf;
}
return format_bytes(buf, l, t);
}
/* Note: the lifetime of the compound literal is the immediately surrounding block,
* see C11 §6.5.2.5, and
* https://stackoverflow.com/questions/34880638/compound-literal-lifetime-and-if-blocks */
#define FORMAT_BYTES(t) format_bytes((char[FORMAT_BYTES_MAX]){}, FORMAT_BYTES_MAX, t)
#define FORMAT_BYTES_FULL(t, flag) format_bytes_full((char[FORMAT_BYTES_MAX]){}, FORMAT_BYTES_MAX, t, flag)
#define FORMAT_BYTES_CGROUP_PROTECTION(t) (t == CGROUP_LIMIT_MAX ? "infinity" : FORMAT_BYTES(t))

View file

@ -10,7 +10,6 @@
#include <unistd.h>
#include "alloc-util.h"
#include "blockdev-util.h"
#include "dirent-util.h"
#include "fd-util.h"
#include "fileio.h"
@ -586,8 +585,6 @@ int get_files_in_directory(const char *path, char ***list) {
return -errno;
FOREACH_DIRENT_ALL(de, d, return -errno) {
dirent_ensure_type(d, de);
if (!dirent_is_file(de))
continue;
@ -756,7 +753,8 @@ bool unsafe_transition(const struct stat *a, const struct stat *b) {
}
static int log_unsafe_transition(int a, int b, const char *path, unsigned flags) {
_cleanup_free_ char *n1 = NULL, *n2 = NULL;
_cleanup_free_ char *n1 = NULL, *n2 = NULL, *user_a = NULL, *user_b = NULL;
struct stat st;
if (!FLAGS_SET(flags, CHASE_WARN))
return -ENOLINK;
@ -764,9 +762,14 @@ static int log_unsafe_transition(int a, int b, const char *path, unsigned flags)
(void) fd_get_path(a, &n1);
(void) fd_get_path(b, &n2);
if (fstat(a, &st) == 0)
user_a = uid_to_name(st.st_uid);
if (fstat(b, &st) == 0)
user_b = uid_to_name(st.st_uid);
return log_warning_errno(SYNTHETIC_ERRNO(ENOLINK),
"Detected unsafe path transition %s %s %s during canonicalization of %s.",
strna(n1), special_glyph(SPECIAL_GLYPH_ARROW), strna(n2), path);
"Detected unsafe path transition %s (owned by %s) %s %s (owned by %s) during canonicalization of %s.",
strna(n1), strna(user_a), special_glyph(SPECIAL_GLYPH_ARROW), strna(n2), strna(user_b), path);
}
static int log_autofs_mount_point(int fd, const char *path, unsigned flags) {
@ -1387,18 +1390,39 @@ int unlinkat_deallocate(int fd, const char *name, UnlinkDeallocateFlags flags) {
#if 0 /* NM_IGNORED */
int fsync_directory_of_file(int fd) {
_cleanup_free_ char *path = NULL;
_cleanup_close_ int dfd = -1;
struct stat st;
int r;
assert(fd >= 0);
/* We only reasonably can do this for regular files and directories, hence check for that */
/* We only reasonably can do this for regular files and directories, or for O_PATH fds, hence check
* for the inode type first */
if (fstat(fd, &st) < 0)
return -errno;
if (S_ISREG(st.st_mode)) {
if (S_ISDIR(st.st_mode)) {
dfd = openat(fd, "..", O_RDONLY|O_DIRECTORY|O_CLOEXEC, 0);
if (dfd < 0)
return -errno;
} else if (!S_ISREG(st.st_mode)) { /* Regular files are OK regardless if O_PATH or not, for all other
* types check O_PATH flag */
int flags;
flags = fcntl(fd, F_GETFL);
if (flags < 0)
return -errno;
if (!FLAGS_SET(flags, O_PATH)) /* If O_PATH this refers to the inode in the fs, in which case
* we can sensibly do what is requested. Otherwise this refers
* to a socket, fifo or device node, where the concept of a
* containing directory doesn't make too much sense. */
return -ENOTTY;
}
if (dfd < 0) {
_cleanup_free_ char *path = NULL;
r = fd_get_path(fd, &path);
if (r < 0) {
@ -1421,13 +1445,7 @@ int fsync_directory_of_file(int fd) {
dfd = open_parent(path, O_CLOEXEC|O_NOFOLLOW, 0);
if (dfd < 0)
return dfd;
} else if (S_ISDIR(st.st_mode)) {
dfd = openat(fd, "..", O_RDONLY|O_DIRECTORY|O_CLOEXEC, 0);
if (dfd < 0)
return -errno;
} else
return -ENOTTY;
}
if (fsync(dfd) < 0)
return -errno;
@ -1479,12 +1497,56 @@ int fsync_path_at(int at_fd, const char *path) {
return 0;
}
int fsync_parent_at(int at_fd, const char *path) {
_cleanup_close_ int opened_fd = -1;
if (isempty(path)) {
if (at_fd != AT_FDCWD)
return fsync_directory_of_file(at_fd);
opened_fd = open("..", O_RDONLY|O_DIRECTORY|O_CLOEXEC);
if (opened_fd < 0)
return -errno;
if (fsync(opened_fd) < 0)
return -errno;
return 0;
}
opened_fd = openat(at_fd, path, O_PATH|O_CLOEXEC|O_NOFOLLOW);
if (opened_fd < 0)
return -errno;
return fsync_directory_of_file(opened_fd);
}
int fsync_path_and_parent_at(int at_fd, const char *path) {
_cleanup_close_ int opened_fd = -1;
if (isempty(path)) {
if (at_fd != AT_FDCWD)
return fsync_full(at_fd);
opened_fd = open(".", O_RDONLY|O_DIRECTORY|O_CLOEXEC);
} else
opened_fd = openat(at_fd, path, O_RDONLY|O_NOFOLLOW|O_NONBLOCK|O_CLOEXEC);
if (opened_fd < 0)
return -errno;
return fsync_full(opened_fd);
}
int syncfs_path(int atfd, const char *path) {
_cleanup_close_ int fd = -1;
assert(path);
if (isempty(path)) {
if (atfd != AT_FDCWD)
return syncfs(atfd) < 0 ? -errno : 0;
fd = openat(atfd, path, O_CLOEXEC|O_RDONLY|O_NONBLOCK);
fd = open(".", O_RDONLY|O_DIRECTORY|O_CLOEXEC);
} else
fd = openat(atfd, path, O_RDONLY|O_CLOEXEC|O_NONBLOCK);
if (fd < 0)
return -errno;
@ -1516,91 +1578,6 @@ int open_parent(const char *path, int flags, mode_t mode) {
return fd;
}
static int blockdev_is_encrypted(const char *sysfs_path, unsigned depth_left) {
_cleanup_free_ char *p = NULL, *uuids = NULL;
_cleanup_closedir_ DIR *d = NULL;
int r, found_encrypted = false;
assert(sysfs_path);
if (depth_left == 0)
return -EINVAL;
p = path_join(sysfs_path, "dm/uuid");
if (!p)
return -ENOMEM;
r = read_one_line_file(p, &uuids);
if (r != -ENOENT) {
if (r < 0)
return r;
/* The DM device's uuid attribute is prefixed with "CRYPT-" if this is a dm-crypt device. */
if (startswith(uuids, "CRYPT-"))
return true;
}
/* Not a dm-crypt device itself. But maybe it is on top of one? Follow the links in the "slaves/"
* subdir. */
p = mfree(p);
p = path_join(sysfs_path, "slaves");
if (!p)
return -ENOMEM;
d = opendir(p);
if (!d) {
if (errno == ENOENT) /* Doesn't have underlying devices */
return false;
return -errno;
}
for (;;) {
_cleanup_free_ char *q = NULL;
struct dirent *de;
errno = 0;
de = readdir_no_dot(d);
if (!de) {
if (errno != 0)
return -errno;
break; /* No more underlying devices */
}
q = path_join(p, de->d_name);
if (!q)
return -ENOMEM;
r = blockdev_is_encrypted(q, depth_left - 1);
if (r < 0)
return r;
if (r == 0) /* we found one that is not encrypted? then propagate that immediately */
return false;
found_encrypted = true;
}
return found_encrypted;
}
int path_is_encrypted(const char *path) {
char p[SYS_BLOCK_PATH_MAX(NULL)];
dev_t devt;
int r;
r = get_block_device(path, &devt);
if (r < 0)
return r;
if (r == 0) /* doesn't have a block device */
return false;
xsprintf_sys_block_path(p, NULL, devt);
return blockdev_is_encrypted(p, 10 /* safety net: maximum recursion depth */);
}
#endif /* NM_IGNORED */
int conservative_renameat(

View file

@ -140,13 +140,13 @@ int unlinkat_deallocate(int fd, const char *name, UnlinkDeallocateFlags flags);
int fsync_directory_of_file(int fd);
int fsync_full(int fd);
int fsync_path_at(int at_fd, const char *path);
int fsync_parent_at(int at_fd, const char *path);
int fsync_path_and_parent_at(int at_fd, const char *path);
int syncfs_path(int atfd, const char *path);
int open_parent(const char *path, int flags, mode_t mode);
int path_is_encrypted(const char *path);
int conservative_renameat(int olddirfd, const char *oldpath, int newdirfd, const char *newpath);
static inline int conservative_rename(const char *oldpath, const char *newpath) {
return conservative_renameat(AT_FDCWD, oldpath, AT_FDCWD, newpath);

View file

@ -114,7 +114,7 @@ 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. */
* All entry types can fit into an ordered_hashmap_entry. */
struct swap_entries {
struct ordered_hashmap_entry e[_IDX_SWAP_END - _IDX_SWAP_BEGIN];
};
@ -1763,6 +1763,9 @@ char** _hashmap_get_strv(HashmapBase *h) {
Iterator i;
unsigned idx, n;
if (!h)
return new0(char*, 1);
sv = new(char*, n_entries(h)+1);
if (!sv)
return NULL;

View file

@ -224,7 +224,7 @@ static inline int ordered_hashmap_remove_and_replace(OrderedHashmap *h, const vo
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
/* Since merging data from an OrderedHashmap into a Hashmap or vice-versa
* should just work, allow this by having looser type-checking here. */
int _hashmap_merge(Hashmap *h, Hashmap *other);
#define hashmap_merge(h, other) _hashmap_merge(PLAIN_HASHMAP(h), PLAIN_HASHMAP(other))

View file

@ -570,38 +570,79 @@ int unbase64char(char c) {
}
#if 0 /* NM_IGNORED */
ssize_t base64mem(const void *p, size_t l, char **out) {
char *r, *z;
static void maybe_line_break(char **x, char *start, size_t line_break) {
size_t n;
assert(x);
assert(*x);
assert(start);
assert(*x >= start);
if (line_break == SIZE_MAX)
return;
n = *x - start;
if (n % (line_break + 1) == line_break)
*((*x)++) = '\n';
}
ssize_t base64mem_full(
const void *p,
size_t l,
size_t line_break,
char **out) {
const uint8_t *x;
char *r, *z;
size_t m;
assert(p || l == 0);
assert(out);
assert(line_break > 0);
/* three input bytes makes four output bytes, padding is added so we must round up */
z = r = malloc(4 * (l + 2) / 3 + 1);
m = 4 * (l + 2) / 3 + 1;
if (line_break != SIZE_MAX)
m += m / line_break;
z = r = malloc(m);
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 */
maybe_line_break(&z, r, line_break);
*(z++) = base64char(x[0] >> 2); /* 00XXXXXX */
maybe_line_break(&z, r, line_break);
*(z++) = base64char((x[0] & 3) << 4 | x[1] >> 4); /* 00XXYYYY */
maybe_line_break(&z, r, line_break);
*(z++) = base64char((x[1] & 15) << 2 | x[2] >> 6); /* 00YYYYZZ */
maybe_line_break(&z, r, line_break);
*(z++) = base64char(x[2] & 63); /* 00ZZZZZZ */
}
switch (l % 3) {
case 2:
maybe_line_break(&z, r, line_break);
*(z++) = base64char(x[0] >> 2); /* 00XXXXXX */
maybe_line_break(&z, r, line_break);
*(z++) = base64char((x[0] & 3) << 4 | x[1] >> 4); /* 00XXYYYY */
maybe_line_break(&z, r, line_break);
*(z++) = base64char((x[1] & 15) << 2); /* 00YYYY00 */
maybe_line_break(&z, r, line_break);
*(z++) = '=';
break;
case 1:
maybe_line_break(&z, r, line_break);
*(z++) = base64char(x[0] >> 2); /* 00XXXXXX */
maybe_line_break(&z, r, line_break);
*(z++) = base64char((x[0] & 3) << 4); /* 00XX0000 */
maybe_line_break(&z, r, line_break);
*(z++) = '=';
maybe_line_break(&z, r, line_break);
*(z++) = '=';
break;
@ -609,6 +650,7 @@ ssize_t base64mem(const void *p, size_t l, char **out) {
*z = 0;
*out = r;
assert(z >= r); /* Let static analyzers know that the answer is non-negative. */
return z - r;
}

View file

@ -33,7 +33,11 @@ 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);
ssize_t base64mem_full(const void *p, size_t l, size_t line_break, char **ret);
static inline ssize_t base64mem(const void *p, size_t l, char **ret) {
return base64mem_full(p, l, SIZE_MAX, ret);
}
int base64_append(char **prefix, int plen,
const void *p, size_t l,
int margin, int width);

View file

@ -30,6 +30,7 @@
#define _weakref_(x) __attribute__((__weakref__(#x)))
#define _alignas_(x) __attribute__((__aligned__(__alignof(x))))
#define _alignptr_ __attribute__((__aligned__(sizeof(void*))))
#define _warn_unused_result_ __attribute__((__warn_unused_result__))
#if __GNUC__ >= 7
#define _fallthrough_ __attribute__((__fallthrough__))
#else
@ -222,7 +223,7 @@ static inline size_t GREEDY_ALLOC_ROUND_UP(size_t l) {
* Contrary to strlen(), this is a constant expression.
* @x: a string literal.
*/
#define STRLEN(x) (sizeof(""x"") - 1)
#define STRLEN(x) ((unsigned) sizeof(""x"") - 1)
/*
* container_of - cast a member of a structure out to the containing structure
@ -350,10 +351,10 @@ static inline int __coverity_check_and_return__(int condition) {
* 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)])))
(2U+(sizeof(type) <= 1 ? 3U : \
sizeof(type) <= 2 ? 5U : \
sizeof(type) <= 4 ? 10U : \
sizeof(type) <= 8 ? 20U : (unsigned) sizeof(int[-2*(sizeof(type) > 8)])))
#define DECIMAL_STR_WIDTH(x) \
({ \

View file

@ -4,7 +4,6 @@
#include <errno.h>
#include <inttypes.h>
#include <linux/oom.h>
#include <net/if.h>
#include <stdio.h>
#include <stdlib.h>
@ -738,7 +737,7 @@ int parse_oom_score_adjust(const char *s, int *ret) {
if (r < 0)
return r;
if (v < OOM_SCORE_ADJ_MIN || v > OOM_SCORE_ADJ_MAX)
if (!oom_score_adjust_is_valid(v))
return -ERANGE;
*ret = v;

View file

@ -472,8 +472,10 @@ char *path_startswith_full(const char *path, const char *prefix, bool accept_dot
int path_compare(const char *a, const char *b) {
int r;
assert(a);
assert(b);
/* Order NULL before non-NULL */
r = CMP(!!a, !!b);
if (r != 0)
return r;
/* A relative path and an absolute path must not compare as equal.
* Which one is sorted before the other does not really matter.
@ -520,15 +522,9 @@ int path_compare(const char *a, const char *b) {
}
}
bool path_equal(const char *a, const char *b) {
return path_compare(a, b) == 0;
}
#if 0 /* NM_IGNORED */
bool path_equal_or_files_same(const char *a, const char *b, int flags) {
return path_equal(a, b) || files_same(a, b, flags) > 0;
}
#endif /* NM_IGNORED */
bool path_equal_filename(const char *a, const char *b) {
_cleanup_free_ char *a_basename = NULL, *b_basename = NULL;

View file

@ -64,7 +64,11 @@ static inline char* path_startswith(const char *path, const char *prefix) {
return path_startswith_full(path, prefix, true);
}
int path_compare(const char *a, const char *b) _pure_;
bool path_equal(const char *a, const char *b) _pure_;
static inline 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, int flags);
/* Compares only the last portion of the input paths, ie: the filenames */
bool path_equal_filename(const char *a, const char *b);

View file

@ -649,20 +649,23 @@ int get_process_environ(pid_t pid, char **env) {
return 0;
}
int get_process_ppid(pid_t pid, pid_t *_ppid) {
int r;
int get_process_ppid(pid_t pid, pid_t *ret) {
_cleanup_free_ char *line = NULL;
long unsigned ppid;
const char *p;
int r;
assert(pid >= 0);
assert(_ppid);
if (pid == 0 || pid == getpid_cached()) {
*_ppid = getppid();
if (ret)
*ret = getppid();
return 0;
}
if (pid == 1) /* PID 1 has no parent, shortcut this case */
return -EADDRNOTAVAIL;
p = procfs_file_alloca(pid, "stat");
r = read_one_line_file(p, &line);
if (r == -ENOENT)
@ -670,9 +673,8 @@ int get_process_ppid(pid_t pid, pid_t *_ppid) {
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 */
/* 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)
@ -686,10 +688,17 @@ int get_process_ppid(pid_t pid, pid_t *_ppid) {
&ppid) != 1)
return -EIO;
if ((long unsigned) (pid_t) ppid != ppid)
/* If ppid is zero the process has no parent. Which might be the case for PID 1 but also for
* processes originating in other namespaces that are inserted into a pidns. Return a recognizable
* error in this case. */
if (ppid == 0)
return -EADDRNOTAVAIL;
if ((pid_t) ppid < 0 || (long unsigned) (pid_t) ppid != ppid)
return -ERANGE;
*_ppid = (pid_t) ppid;
if (ret)
*ret = (pid_t) ppid;
return 0;
}
@ -1036,30 +1045,6 @@ bool is_main_thread(void) {
}
#if 0 /* NM_IGNORED */
_noreturn_ void freeze(void) {
log_close();
/* Make sure nobody waits for us on a socket anymore */
(void) close_all_fds(NULL, 0);
sync();
/* Let's not freeze right away, but keep reaping zombies. */
for (;;) {
int r;
siginfo_t si = {};
r = waitid(P_ALL, 0, &si, WEXITED);
if (r < 0 && errno != EINTR)
break;
}
/* waitid() failed with an unexpected error, things are really borked. Freeze now! */
for (;;)
pause();
}
bool oom_score_adjust_is_valid(int oa) {
return oa >= OOM_SCORE_ADJ_MIN && oa <= OOM_SCORE_ADJ_MAX;
}
@ -1272,7 +1257,7 @@ static void restore_sigsetp(sigset_t **ssp) {
int safe_fork_full(
const char *name,
const int except_fds[],
int except_fds[],
size_t n_except_fds,
ForkFlags flags,
pid_t *ret_pid) {
@ -1467,7 +1452,7 @@ int safe_fork_full(
int namespace_fork(
const char *outer_name,
const char *inner_name,
const int except_fds[],
int except_fds[],
size_t n_except_fds,
ForkFlags flags,
int pidns_fd,
@ -1483,7 +1468,8 @@ int namespace_fork(
* process. This ensures that we are fully a member of the destination namespace, with pidns an all, so that
* /proc/self/fd works correctly. */
r = safe_fork_full(outer_name, except_fds, n_except_fds, (flags|FORK_DEATHSIG) & ~(FORK_REOPEN_LOG|FORK_NEW_MOUNTNS|FORK_MOUNTNS_SLAVE), ret_pid);
r = safe_fork_full(outer_name, except_fds, n_except_fds,
(flags|FORK_DEATHSIG) & ~(FORK_REOPEN_LOG|FORK_NEW_MOUNTNS|FORK_MOUNTNS_SLAVE), ret_pid);
if (r < 0)
return r;
if (r == 0) {
@ -1518,86 +1504,10 @@ int namespace_fork(
return 1;
}
int fork_agent(const char *name, const int except[], size_t n_except, pid_t *ret_pid, const char *path, ...) {
bool stdout_is_tty, stderr_is_tty;
size_t n, i;
va_list ap;
char **l;
int r;
assert(path);
/* Spawns a temporary TTY agent, making sure it goes away when we go away */
r = safe_fork_full(name,
except,
n_except,
FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_CLOSE_ALL_FDS|FORK_REOPEN_LOG,
ret_pid);
if (r < 0)
return r;
if (r > 0)
return 0;
/* In the child: */
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);
}
safe_close_above_stdio(fd);
}
(void) rlimit_nofile_safe();
/* Count arguments */
va_start(ap, path);
for (n = 0; va_arg(ap, char*); n++)
;
va_end(ap);
/* Allocate strv */
l = newa(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);
}
int set_oom_score_adjust(int value) {
char t[DECIMAL_STR_MAX(int)];
sprintf(t, "%i", value);
xsprintf(t, "%i", value);
return write_string_file("/proc/self/oom_score_adj", t,
WRITE_STRING_FILE_VERIFY_ON_FAILURE|WRITE_STRING_FILE_DISABLE_BUFFER);

View file

@ -82,8 +82,6 @@ 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
@ -170,15 +168,13 @@ typedef enum ForkFlags {
FORK_NEW_USERNS = 1 << 13, /* Run child in its own user namespace */
} ForkFlags;
int safe_fork_full(const char *name, const int except_fds[], size_t n_except_fds, ForkFlags flags, pid_t *ret_pid);
int safe_fork_full(const char *name, int except_fds[], size_t n_except_fds, ForkFlags flags, pid_t *ret_pid);
static inline int safe_fork(const char *name, ForkFlags flags, pid_t *ret_pid) {
return safe_fork_full(name, NULL, 0, flags, ret_pid);
}
int namespace_fork(const char *outer_name, const char *inner_name, const int except_fds[], size_t n_except_fds, ForkFlags flags, int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int root_fd, pid_t *ret_pid);
int fork_agent(const char *name, const int except[], size_t n_except, pid_t *pid, const char *path, ...) _sentinel_;
int namespace_fork(const char *outer_name, const char *inner_name, int except_fds[], size_t n_except_fds, ForkFlags flags, int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int root_fd, pid_t *ret_pid);
int set_oom_score_adjust(int value);

View file

@ -241,7 +241,6 @@ int signal_from_string(const char *s) {
void nop_signal_handler(int sig) {
/* nothing here */
}
#endif /* NM_IGNORED */
int signal_is_blocked(int sig) {
sigset_t ss;
@ -257,3 +256,39 @@ int signal_is_blocked(int sig) {
return r;
}
int pop_pending_signal_internal(int sig, ...) {
sigset_t ss;
va_list ap;
int r;
if (sig < 0) /* Empty list? */
return -EINVAL;
if (sigemptyset(&ss) < 0)
return -errno;
/* Add first signal (if the signal is zero, we'll silently skip it, to make it easiert to build
* parameter lists where some element are sometimes off, similar to how sigset_add_many_ap() handles
* this.) */
if (sig > 0 && sigaddset(&ss, sig) < 0)
return -errno;
/* Add all other signals */
va_start(ap, sig);
r = sigset_add_many_ap(&ss, ap);
va_end(ap);
if (r < 0)
return r;
r = sigtimedwait(&ss, NULL, &(struct timespec) { 0, 0 });
if (r < 0) {
if (errno == EAGAIN)
return 0;
return -errno;
}
return r; /* Returns the signal popped */
}
#endif /* NM_IGNORED */

View file

@ -62,3 +62,6 @@ static inline const char* signal_to_string_with_check(int n) {
}
int signal_is_blocked(int sig);
int pop_pending_signal_internal(int sig, ...);
#define pop_pending_signal(...) pop_pending_signal_internal(__VA_ARGS__, -1)

View file

@ -754,6 +754,22 @@ static const char* const ip_tos_table[] = {
DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(ip_tos, int, 0xff);
#endif /* NM_IGNORED */
bool ifname_valid_char(char a) {
if ((unsigned char) a >= 127U)
return false;
if ((unsigned char) a <= 32U)
return false;
if (IN_SET(a,
':', /* colons are used by the legacy "alias" interface logic */
'/', /* slashes cannot work, since we need to use network interfaces in sysfs paths, and in paths slashes are separators */
'%')) /* %d is used in the kernel's weird foo%d format string naming feature which we really really don't want to ever run into by accident */
return false;
return true;
}
bool ifname_valid_full(const char *p, IfnameValidFlags flags) {
bool numeric = true;
@ -787,16 +803,7 @@ bool ifname_valid_full(const char *p, IfnameValidFlags flags) {
return false;
for (const char *t = p; *t; t++) {
if ((unsigned char) *t >= 127U)
return false;
if ((unsigned char) *t <= 32U)
return false;
if (IN_SET(*t,
':', /* colons are used by the legacy "alias" interface logic */
'/', /* slashes cannot work, since we need to use network interfaces in sysfs paths, and in paths slashes are separators */
'%')) /* %d is used in the kernel's weird foo%d format string naming feature which we really really don't want to ever run into by accident */
if (!ifname_valid_char(*t))
return false;
numeric = numeric && (*t >= '0' && *t <= '9');

View file

@ -141,6 +141,7 @@ typedef enum {
IFNAME_VALID_NUMERIC = 1 << 1,
_IFNAME_VALID_ALL = IFNAME_VALID_ALTERNATIVE | IFNAME_VALID_NUMERIC,
} IfnameValidFlags;
bool ifname_valid_char(char a);
bool ifname_valid_full(const char *p, IfnameValidFlags flags);
static inline bool ifname_valid(const char *p) {
return ifname_valid_full(p, 0);

View file

@ -159,6 +159,7 @@ int path_is_read_only_fs(const char *path) {
return false;
}
#endif /* NM_IGNORED */
int files_same(const char *filea, const char *fileb, int flags) {
struct stat a, b;
@ -176,6 +177,7 @@ int files_same(const char *filea, const char *fileb, int flags) {
a.st_ino == b.st_ino;
}
#if 0 /* NM_IGNORED */
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));

View file

@ -831,6 +831,7 @@ char **strv_reverse(char **l) {
return l;
}
#if 0 /* NM_IGNORED */
char **strv_shell_escape(char **l, const char *bad) {
char **s;
@ -850,6 +851,7 @@ char **strv_shell_escape(char **l, const char *bad) {
return l;
}
#endif /* NM_IGNORED */
bool strv_fnmatch_full(char* const* patterns, const char *s, int flags, size_t *matched_pos) {
for (size_t i = 0; patterns && patterns[i]; i++)

View file

@ -432,19 +432,37 @@ char *format_timestamp_relative(char *buf, size_t l, usec_t t) {
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)
if (d >= USEC_PER_YEAR) {
usec_t years = d / USEC_PER_YEAR;
usec_t months = (d % USEC_PER_YEAR) / USEC_PER_MONTH;
snprintf(buf, l, USEC_FMT " %s " USEC_FMT " %s %s",
years,
years == 1 ? "year" : "years",
months,
months == 1 ? "month" : "months",
s);
} else if (d >= USEC_PER_MONTH) {
usec_t months = d / USEC_PER_MONTH;
usec_t days = (d % USEC_PER_MONTH) / USEC_PER_DAY;
snprintf(buf, l, USEC_FMT " %s " USEC_FMT " %s %s",
months,
months == 1 ? "month" : "months",
days,
days == 1 ? "day" : "days",
s);
} else if (d >= USEC_PER_WEEK) {
usec_t weeks = d / USEC_PER_WEEK;
usec_t days = (d % USEC_PER_WEEK) / USEC_PER_DAY;
snprintf(buf, l, USEC_FMT " %s " USEC_FMT " %s %s",
weeks,
weeks == 1 ? "week" : "weeks",
days,
days == 1 ? "day" : "days",
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",
@ -539,14 +557,12 @@ char *format_timespan(char *buf, size_t l, usec_t t, usec_t accuracy) {
/* Let's see if we should shows this in dot notation */
if (t < USEC_PER_MINUTE && b > 0) {
usec_t cc;
signed char j;
signed char j = 0;
j = 0;
for (cc = table[i].usec; cc > 1; cc /= 10)
for (usec_t cc = table[i].usec; cc > 1; cc /= 10)
j++;
for (cc = accuracy; cc > 1; cc /= 10) {
for (usec_t cc = accuracy; cc > 1; cc /= 10) {
b /= 10;
j--;
}
@ -1249,75 +1265,127 @@ int parse_nsec(const char *t, nsec_t *nsec) {
return 0;
}
int get_timezones(char ***ret) {
static int get_timezones_from_zone1970_tab(char ***ret) {
_cleanup_fclose_ FILE *f = NULL;
_cleanup_strv_free_ char **zones = NULL;
size_t n_zones = 0;
int r;
assert(ret);
zones = strv_new("UTC");
if (!zones)
return -ENOMEM;
n_zones = 1;
f = fopen("/usr/share/zoneinfo/zone1970.tab", "re");
if (f) {
for (;;) {
_cleanup_free_ char *line = NULL, *w = NULL;
char *p;
size_t k;
r = read_line(f, LONG_LINE_MAX, &line);
if (r < 0)
return r;
if (r == 0)
break;
p = strstrip(line);
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_zones + 2))
return -ENOMEM;
zones[n_zones++] = TAKE_PTR(w);
zones[n_zones] = NULL;
}
strv_sort(zones);
strv_uniq(zones);
} else if (errno != ENOENT)
if (!f)
return -errno;
*ret = TAKE_PTR(zones);
for (;;) {
_cleanup_free_ char *line = NULL, *cc = NULL, *co = NULL, *tz = NULL;
r = read_line(f, LONG_LINE_MAX, &line);
if (r < 0)
return r;
if (r == 0)
break;
const char *p = line;
/* Line format is:
* 'country codes' 'coordinates' 'timezone' 'comments' */
r = extract_many_words(&p, NULL, 0, &cc, &co, &tz, NULL);
if (r < 0)
continue;
/* Lines that start with # are comments. */
if (*cc == '#')
continue;
r = strv_extend(&zones, tz);
if (r < 0)
return r;
}
*ret = TAKE_PTR(zones);
return 0;
}
static int get_timezones_from_tzdata_zi(char ***ret) {
_cleanup_fclose_ FILE *f = NULL;
_cleanup_strv_free_ char **zones = NULL;
int r;
f = fopen("/usr/share/zoneinfo/tzdata.zi", "re");
if (!f)
return -errno;
for (;;) {
_cleanup_free_ char *line = NULL, *type = NULL, *f1 = NULL, *f2 = NULL;
r = read_line(f, LONG_LINE_MAX, &line);
if (r < 0)
return r;
if (r == 0)
break;
const char *p = line;
/* The only lines we care about are Zone and Link lines.
* Zone line format is:
* 'Zone' 'timezone' ...
* Link line format is:
* 'Link' 'target' 'alias'
* See 'man zic' for more detail. */
r = extract_many_words(&p, NULL, 0, &type, &f1, &f2, NULL);
if (r < 0)
continue;
char *tz;
if (IN_SET(*type, 'Z', 'z'))
/* Zone lines have timezone in field 1. */
tz = f1;
else if (IN_SET(*type, 'L', 'l'))
/* Link lines have timezone in field 2. */
tz = f2;
else
/* Not a line we care about. */
continue;
r = strv_extend(&zones, tz);
if (r < 0)
return r;
}
*ret = TAKE_PTR(zones);
return 0;
}
int get_timezones(char ***ret) {
_cleanup_strv_free_ char **zones = NULL;
int r;
assert(ret);
r = get_timezones_from_tzdata_zi(&zones);
if (r == -ENOENT) {
log_debug_errno(r, "Could not get timezone data from tzdata.zi, using zone1970.tab: %m");
r = get_timezones_from_zone1970_tab(&zones);
if (r == -ENOENT)
log_debug_errno(r, "Could not get timezone data from zone1970.tab, using UTC: %m");
}
if (r < 0 && r != -ENOENT)
return r;
/* Always include UTC */
r = strv_extend(&zones, "UTC");
if (r < 0)
return -ENOMEM;
strv_sort(zones);
strv_uniq(zones);
*ret = TAKE_PTR(zones);
return 0;
}
#endif /* NM_IGNORED */
bool timezone_is_valid(const char *name, int log_level) {
int verify_timezone(const char *name, int log_level) {
bool slash = false;
const char *p, *t;
_cleanup_close_ int fd = -1;
@ -1325,26 +1393,26 @@ bool timezone_is_valid(const char *name, int log_level) {
int r;
if (isempty(name))
return false;
return -EINVAL;
/* Always accept "UTC" as valid timezone, since it's the fallback, even if user has no timezones installed. */
if (streq(name, "UTC"))
return true;
return 0;
if (name[0] == '/')
return false;
return -EINVAL;
for (p = name; *p; p++) {
if (!(*p >= '0' && *p <= '9') &&
!(*p >= 'a' && *p <= 'z') &&
!(*p >= 'A' && *p <= 'Z') &&
!IN_SET(*p, '-', '_', '+', '/'))
return false;
return -EINVAL;
if (*p == '/') {
if (slash)
return false;
return -EINVAL;
slash = true;
} else
@ -1352,38 +1420,31 @@ bool timezone_is_valid(const char *name, int log_level) {
}
if (slash)
return false;
return -EINVAL;
if (p - name >= PATH_MAX)
return false;
return -ENAMETOOLONG;
t = strjoina("/usr/share/zoneinfo/", name);
fd = open(t, O_RDONLY|O_CLOEXEC);
if (fd < 0) {
log_full_errno(log_level, errno, "Failed to open timezone file '%s': %m", t);
return false;
}
if (fd < 0)
return log_full_errno(log_level, errno, "Failed to open timezone file '%s': %m", t);
r = fd_verify_regular(fd);
if (r < 0) {
log_full_errno(log_level, r, "Timezone file '%s' is not a regular file: %m", t);
return false;
}
if (r < 0)
return log_full_errno(log_level, r, "Timezone file '%s' is not a regular file: %m", t);
r = loop_read_exact(fd, buf, 4, false);
if (r < 0) {
log_full_errno(log_level, r, "Failed to read from timezone file '%s': %m", t);
return false;
}
if (r < 0)
return log_full_errno(log_level, r, "Failed to read from timezone file '%s': %m", t);
/* Magic from tzfile(5) */
if (memcmp(buf, "TZif", 4) != 0) {
log_full(log_level, "Timezone file '%s' has wrong magic bytes", t);
return false;
}
if (memcmp(buf, "TZif", 4) != 0)
return log_full_errno(log_level, SYNTHETIC_ERRNO(EBADMSG),
"Timezone file '%s' has wrong magic bytes", t);
return true;
return 0;
}
bool clock_boottime_supported(void) {

View file

@ -111,20 +111,31 @@ usec_t triple_timestamp_by_clock(triple_timestamp *ts, clockid_t clock);
usec_t timespec_load(const struct timespec *ts) _pure_;
nsec_t timespec_load_nsec(const struct timespec *ts) _pure_;
struct timespec *timespec_store(struct timespec *ts, usec_t u);
struct timespec *timespec_store_nsec(struct timespec *ts, nsec_t n);
struct timespec* timespec_store(struct timespec *ts, usec_t u);
struct timespec* timespec_store_nsec(struct timespec *ts, nsec_t n);
usec_t timeval_load(const struct timeval *tv) _pure_;
struct timeval *timeval_store(struct timeval *tv, usec_t u);
struct timeval* timeval_store(struct timeval *tv, usec_t u);
char *format_timestamp_style(char *buf, size_t l, usec_t t, TimestampStyle style);
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);
char* format_timestamp_style(char *buf, size_t l, usec_t t, TimestampStyle style) _warn_unused_result_;
char* format_timestamp_relative(char *buf, size_t l, usec_t t) _warn_unused_result_;
char* format_timespan(char *buf, size_t l, usec_t t, usec_t accuracy) _warn_unused_result_;
static inline char *format_timestamp(char *buf, size_t l, usec_t t) {
_warn_unused_result_
static inline char* format_timestamp(char *buf, size_t l, usec_t t) {
return format_timestamp_style(buf, l, t, TIMESTAMP_PRETTY);
}
/* Note: the lifetime of the compound literal is the immediately surrounding block,
* see C11 §6.5.2.5, and
* https://stackoverflow.com/questions/34880638/compound-literal-lifetime-and-if-blocks */
#define FORMAT_TIMESTAMP(t) format_timestamp((char[FORMAT_TIMESTAMP_MAX]){}, FORMAT_TIMESTAMP_MAX, t)
#define FORMAT_TIMESTAMP_RELATIVE(t) \
format_timestamp_relative((char[FORMAT_TIMESTAMP_RELATIVE_MAX]){}, FORMAT_TIMESTAMP_RELATIVE_MAX, t)
#define FORMAT_TIMESPAN(t, accuracy) format_timespan((char[FORMAT_TIMESPAN_MAX]){}, FORMAT_TIMESPAN_MAX, t, accuracy)
#define FORMAT_TIMESTAMP_STYLE(t, style) \
format_timestamp_style((char[FORMAT_TIMESTAMP_MAX]){}, FORMAT_TIMESTAMP_MAX, t, style)
int parse_timestamp(const char *t, usec_t *usec);
int parse_sec(const char *t, usec_t *usec);
@ -134,7 +145,10 @@ int parse_time(const char *t, usec_t *usec, usec_t default_unit);
int parse_nsec(const char *t, nsec_t *nsec);
int get_timezones(char ***l);
bool timezone_is_valid(const char *name, int log_level);
int verify_timezone(const char *name, int log_level);
static inline bool timezone_is_valid(const char *name, int log_level) {
return verify_timezone(name, log_level) >= 0;
}
bool clock_boottime_supported(void);
bool clock_supported(clockid_t clock);
@ -153,9 +167,8 @@ usec_t jiffies_to_usec(uint32_t jiffies);
bool in_utc_timezone(void);
static inline usec_t usec_add(usec_t a, usec_t b) {
/* Adds two time values, and makes sure USEC_INFINITY as input results as USEC_INFINITY in output, and doesn't
* overflow. */
/* Adds two time values, and makes sure USEC_INFINITY as input results as USEC_INFINITY in output,
* and doesn't overflow. */
if (a > USEC_INFINITY - b) /* overflow check */
return USEC_INFINITY;
@ -164,7 +177,6 @@ static inline usec_t usec_add(usec_t a, usec_t b) {
}
static inline usec_t usec_sub_unsigned(usec_t timestamp, usec_t delta) {
if (timestamp == USEC_INFINITY) /* Make sure infinity doesn't degrade */
return USEC_INFINITY;
if (timestamp < delta)
@ -181,14 +193,14 @@ static inline usec_t usec_sub_signed(usec_t timestamp, int64_t delta) {
}
#if SIZEOF_TIME_T == 8
/* The last second we can format is 31. Dec 9999, 1s before midnight, because otherwise we'd enter 5 digit year
* territory. However, since we want to stay away from this in all timezones we take one day off. */
#define USEC_TIMESTAMP_FORMATTABLE_MAX ((usec_t) 253402214399000000)
/* The last second we can format is 31. Dec 9999, 1s before midnight, because otherwise we'd enter 5 digit
* year territory. However, since we want to stay away from this in all timezones we take one day off. */
# define USEC_TIMESTAMP_FORMATTABLE_MAX ((usec_t) 253402214399000000)
#elif SIZEOF_TIME_T == 4
/* With a 32bit time_t we can't go beyond 2038... */
#define USEC_TIMESTAMP_FORMATTABLE_MAX ((usec_t) 2147483647000000)
# define USEC_TIMESTAMP_FORMATTABLE_MAX ((usec_t) 2147483647000000)
#else
#error "Yuck, time_t is neither 4 nor 8 bytes wide?"
# error "Yuck, time_t is neither 4 nor 8 bytes wide?"
#endif
int time_change_fd(void);