diff options
Diffstat (limited to 'src/shared')
117 files changed, 32276 insertions, 0 deletions
diff --git a/src/shared/MurmurHash3.c b/src/shared/MurmurHash3.c new file mode 100644 index 0000000000..aa1387373f --- /dev/null +++ b/src/shared/MurmurHash3.c @@ -0,0 +1,349 @@ +//----------------------------------------------------------------------------- +// MurmurHash3 was written by Austin Appleby, and is placed in the public +// domain. The author hereby disclaims copyright to this source code. + +// Note - The x86 and x64 versions do _not_ produce the same results, as the +// algorithms are optimized for their respective platforms. You can still +// compile and run any of them on any platform, but your performance with the +// non-native version will be less than optimal. + +#include "MurmurHash3.h" + +//----------------------------------------------------------------------------- +// Platform-specific functions and macros + +// Microsoft Visual Studio + +#if defined(_MSC_VER) + +#define FORCE_INLINE __forceinline + +#include <stdlib.h> + +#define ROTL32(x,y) _rotl(x,y) +#define ROTL64(x,y) _rotl64(x,y) + +#define BIG_CONSTANT(x) (x) + +// Other compilers + +#else // defined(_MSC_VER) + +#define FORCE_INLINE inline __attribute__((always_inline)) + +static inline uint32_t rotl32 ( uint32_t x, int8_t r ) +{ + return (x << r) | (x >> (32 - r)); +} + +static inline uint64_t rotl64 ( uint64_t x, int8_t r ) +{ + return (x << r) | (x >> (64 - r)); +} + +#define ROTL32(x,y) rotl32(x,y) +#define ROTL64(x,y) rotl64(x,y) + +#define BIG_CONSTANT(x) (x##LLU) + +#endif // !defined(_MSC_VER) + +//----------------------------------------------------------------------------- +// Block read - if your platform needs to do endian-swapping or can only +// handle aligned reads, do the conversion here + +static FORCE_INLINE uint32_t getblock32 ( const uint32_t * p, int i ) +{ + return p[i]; +} + +static FORCE_INLINE uint64_t getblock64 ( const uint64_t * p, int i ) +{ + return p[i]; +} + +//----------------------------------------------------------------------------- +// Finalization mix - force all bits of a hash block to avalanche + +static FORCE_INLINE uint32_t fmix32 ( uint32_t h ) +{ + h ^= h >> 16; + h *= 0x85ebca6b; + h ^= h >> 13; + h *= 0xc2b2ae35; + h ^= h >> 16; + + return h; +} + +//---------- + +static FORCE_INLINE uint64_t fmix64 ( uint64_t k ) +{ + k ^= k >> 33; + k *= BIG_CONSTANT(0xff51afd7ed558ccd); + k ^= k >> 33; + k *= BIG_CONSTANT(0xc4ceb9fe1a85ec53); + k ^= k >> 33; + + return k; +} + +//----------------------------------------------------------------------------- + +void MurmurHash3_x86_32 ( const void * key, size_t len, + uint32_t seed, void * out ) +{ + const uint8_t * data = (const uint8_t*)key; + const int nblocks = len / 4; + + uint32_t h1 = seed; + + const uint32_t c1 = 0xcc9e2d51; + const uint32_t c2 = 0x1b873593; + + const uint8_t * tail; + uint32_t k1; + + //---------- + // body + + const uint32_t * blocks = (const uint32_t *)(data + nblocks*4); + + for(int i = -nblocks; i; i++) + { + k1 = getblock32(blocks,i); + + k1 *= c1; + k1 = ROTL32(k1,15); + k1 *= c2; + + h1 ^= k1; + h1 = ROTL32(h1,13); + h1 = h1*5+0xe6546b64; + } + + //---------- + // tail + + tail = (const uint8_t*)(data + nblocks*4); + + k1 = 0; + + switch(len & 3) + { + case 3: k1 ^= tail[2] << 16; + case 2: k1 ^= tail[1] << 8; + case 1: k1 ^= tail[0]; + k1 *= c1; k1 = ROTL32(k1,15); k1 *= c2; h1 ^= k1; + }; + + //---------- + // finalization + + h1 ^= len; + + h1 = fmix32(h1); + + *(uint32_t*)out = h1; +} + +//----------------------------------------------------------------------------- + +void MurmurHash3_x86_128 ( const void * key, const size_t len, + uint32_t seed, void * out ) +{ + const uint8_t * data = (const uint8_t*)key; + const int nblocks = len / 16; + + uint32_t h1 = seed; + uint32_t h2 = seed; + uint32_t h3 = seed; + uint32_t h4 = seed; + + const uint32_t c1 = 0x239b961b; + const uint32_t c2 = 0xab0e9789; + const uint32_t c3 = 0x38b34ae5; + const uint32_t c4 = 0xa1e38b93; + + const uint8_t * tail; + + uint32_t k1; + uint32_t k2; + uint32_t k3; + uint32_t k4; + + //---------- + // body + + const uint32_t * blocks = (const uint32_t *)(data + nblocks*16); + + for(int i = -nblocks; i; i++) + { + k1 = getblock32(blocks,i*4+0); + k2 = getblock32(blocks,i*4+1); + k3 = getblock32(blocks,i*4+2); + k4 = getblock32(blocks,i*4+3); + + k1 *= c1; k1 = ROTL32(k1,15); k1 *= c2; h1 ^= k1; + + h1 = ROTL32(h1,19); h1 += h2; h1 = h1*5+0x561ccd1b; + + k2 *= c2; k2 = ROTL32(k2,16); k2 *= c3; h2 ^= k2; + + h2 = ROTL32(h2,17); h2 += h3; h2 = h2*5+0x0bcaa747; + + k3 *= c3; k3 = ROTL32(k3,17); k3 *= c4; h3 ^= k3; + + h3 = ROTL32(h3,15); h3 += h4; h3 = h3*5+0x96cd1c35; + + k4 *= c4; k4 = ROTL32(k4,18); k4 *= c1; h4 ^= k4; + + h4 = ROTL32(h4,13); h4 += h1; h4 = h4*5+0x32ac3b17; + } + + //---------- + // tail + + tail = (const uint8_t*)(data + nblocks*16); + + k1 = 0; + k2 = 0; + k3 = 0; + k4 = 0; + + switch(len & 15) + { + case 15: k4 ^= tail[14] << 16; + case 14: k4 ^= tail[13] << 8; + case 13: k4 ^= tail[12] << 0; + k4 *= c4; k4 = ROTL32(k4,18); k4 *= c1; h4 ^= k4; + + case 12: k3 ^= tail[11] << 24; + case 11: k3 ^= tail[10] << 16; + case 10: k3 ^= tail[ 9] << 8; + case 9: k3 ^= tail[ 8] << 0; + k3 *= c3; k3 = ROTL32(k3,17); k3 *= c4; h3 ^= k3; + + case 8: k2 ^= tail[ 7] << 24; + case 7: k2 ^= tail[ 6] << 16; + case 6: k2 ^= tail[ 5] << 8; + case 5: k2 ^= tail[ 4] << 0; + k2 *= c2; k2 = ROTL32(k2,16); k2 *= c3; h2 ^= k2; + + case 4: k1 ^= tail[ 3] << 24; + case 3: k1 ^= tail[ 2] << 16; + case 2: k1 ^= tail[ 1] << 8; + case 1: k1 ^= tail[ 0] << 0; + k1 *= c1; k1 = ROTL32(k1,15); k1 *= c2; h1 ^= k1; + }; + + //---------- + // finalization + + h1 ^= len; h2 ^= len; h3 ^= len; h4 ^= len; + + h1 += h2; h1 += h3; h1 += h4; + h2 += h1; h3 += h1; h4 += h1; + + h1 = fmix32(h1); + h2 = fmix32(h2); + h3 = fmix32(h3); + h4 = fmix32(h4); + + h1 += h2; h1 += h3; h1 += h4; + h2 += h1; h3 += h1; h4 += h1; + + ((uint32_t*)out)[0] = h1; + ((uint32_t*)out)[1] = h2; + ((uint32_t*)out)[2] = h3; + ((uint32_t*)out)[3] = h4; +} + +//----------------------------------------------------------------------------- + +void MurmurHash3_x64_128 ( const void * key, const size_t len, + const uint32_t seed, void * out ) +{ + const uint8_t * data = (const uint8_t*)key; + const int nblocks = len / 16; + + uint64_t h1 = seed; + uint64_t h2 = seed; + + const uint64_t c1 = BIG_CONSTANT(0x87c37b91114253d5); + const uint64_t c2 = BIG_CONSTANT(0x4cf5ad432745937f); + + const uint8_t *tail; + + uint64_t k1; + uint64_t k2; + + //---------- + // body + + const uint64_t * blocks = (const uint64_t *)(data); + + for(int i = 0; i < nblocks; i++) + { + k1 = getblock64(blocks,i*2+0); + k2 = getblock64(blocks,i*2+1); + + k1 *= c1; k1 = ROTL64(k1,31); k1 *= c2; h1 ^= k1; + + h1 = ROTL64(h1,27); h1 += h2; h1 = h1*5+0x52dce729; + + k2 *= c2; k2 = ROTL64(k2,33); k2 *= c1; h2 ^= k2; + + h2 = ROTL64(h2,31); h2 += h1; h2 = h2*5+0x38495ab5; + } + + //---------- + // tail + + tail = (const uint8_t*)(data + nblocks*16); + + k1 = 0; + k2 = 0; + + switch(len & 15) + { + case 15: k2 ^= (uint64_t)(tail[14]) << 48; + case 14: k2 ^= (uint64_t)(tail[13]) << 40; + case 13: k2 ^= (uint64_t)(tail[12]) << 32; + case 12: k2 ^= (uint64_t)(tail[11]) << 24; + case 11: k2 ^= (uint64_t)(tail[10]) << 16; + case 10: k2 ^= (uint64_t)(tail[ 9]) << 8; + case 9: k2 ^= (uint64_t)(tail[ 8]) << 0; + k2 *= c2; k2 = ROTL64(k2,33); k2 *= c1; h2 ^= k2; + + case 8: k1 ^= (uint64_t)(tail[ 7]) << 56; + case 7: k1 ^= (uint64_t)(tail[ 6]) << 48; + case 6: k1 ^= (uint64_t)(tail[ 5]) << 40; + case 5: k1 ^= (uint64_t)(tail[ 4]) << 32; + case 4: k1 ^= (uint64_t)(tail[ 3]) << 24; + case 3: k1 ^= (uint64_t)(tail[ 2]) << 16; + case 2: k1 ^= (uint64_t)(tail[ 1]) << 8; + case 1: k1 ^= (uint64_t)(tail[ 0]) << 0; + k1 *= c1; k1 = ROTL64(k1,31); k1 *= c2; h1 ^= k1; + }; + + //---------- + // finalization + + h1 ^= len; h2 ^= len; + + h1 += h2; + h2 += h1; + + h1 = fmix64(h1); + h2 = fmix64(h2); + + h1 += h2; + h2 += h1; + + ((uint64_t*)out)[0] = h1; + ((uint64_t*)out)[1] = h2; +} + +//----------------------------------------------------------------------------- diff --git a/src/shared/MurmurHash3.h b/src/shared/MurmurHash3.h new file mode 100644 index 0000000000..07c474eba4 --- /dev/null +++ b/src/shared/MurmurHash3.h @@ -0,0 +1,38 @@ +//----------------------------------------------------------------------------- +// MurmurHash3 was written by Austin Appleby, and is placed in the public +// domain. The author hereby disclaims copyright to this source code. + +#ifndef _MURMURHASH3_H_ +#define _MURMURHASH3_H_ + +//----------------------------------------------------------------------------- +// Platform-specific functions and macros + +// Microsoft Visual Studio + +#if defined(_MSC_VER) + +typedef unsigned char uint8_t; +typedef unsigned long uint32_t; +typedef unsigned __int64 uint64_t; + +// Other compilers + +#else // defined(_MSC_VER) + +#include <stdint.h> +#include <sys/types.h> + +#endif // !defined(_MSC_VER) + +//----------------------------------------------------------------------------- + +void MurmurHash3_x86_32 ( const void * key, size_t len, uint32_t seed, void * out ); + +void MurmurHash3_x86_128 ( const void * key, size_t len, uint32_t seed, void * out ); + +void MurmurHash3_x64_128 ( const void * key, size_t len, uint32_t seed, void * out ); + +//----------------------------------------------------------------------------- + +#endif // _MURMURHASH3_H_ diff --git a/src/shared/acl-util.c b/src/shared/acl-util.c new file mode 100644 index 0000000000..48bb12f46b --- /dev/null +++ b/src/shared/acl-util.c @@ -0,0 +1,126 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2011,2013 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <assert.h> +#include <sys/acl.h> +#include <acl/libacl.h> +#include <errno.h> +#include <stdbool.h> + +#include "acl-util.h" +#include "util.h" +#include "strv.h" + +int acl_find_uid(acl_t acl, uid_t uid, acl_entry_t *entry) { + acl_entry_t i; + int found; + + assert(acl); + assert(entry); + + for (found = acl_get_entry(acl, ACL_FIRST_ENTRY, &i); + found > 0; + found = acl_get_entry(acl, ACL_NEXT_ENTRY, &i)) { + + acl_tag_t tag; + uid_t *u; + bool b; + + if (acl_get_tag_type(i, &tag) < 0) + return -errno; + + if (tag != ACL_USER) + continue; + + u = acl_get_qualifier(i); + if (!u) + return -errno; + + b = *u == uid; + acl_free(u); + + if (b) { + *entry = i; + return 1; + } + } + + if (found < 0) + return -errno; + + return 0; +} + +int search_acl_groups(char*** dst, const char* path, bool* belong) { + acl_t acl; + + assert(path); + assert(belong); + + acl = acl_get_file(path, ACL_TYPE_DEFAULT); + if (acl) { + acl_entry_t entry; + int r; + + r = acl_get_entry(acl, ACL_FIRST_ENTRY, &entry); + while (r > 0) { + acl_tag_t tag; + gid_t *gid; + char *name; + + r = acl_get_tag_type(entry, &tag); + if (r < 0) + break; + + if (tag != ACL_GROUP) + goto next; + + gid = acl_get_qualifier(entry); + if (!gid) + break; + + if (in_gid(*gid) > 0) { + *belong = true; + break; + } + + name = gid_to_name(*gid); + if (!name) { + acl_free(acl); + return log_oom(); + } + + r = strv_push(dst, name); + if (r < 0) { + free(name); + acl_free(acl); + return log_oom(); + } + + next: + r = acl_get_entry(acl, ACL_NEXT_ENTRY, &entry); + } + + acl_free(acl); + } + + return 0; +} diff --git a/src/shared/acl-util.h b/src/shared/acl-util.h new file mode 100644 index 0000000000..23090d9984 --- /dev/null +++ b/src/shared/acl-util.h @@ -0,0 +1,27 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <stdbool.h> + +int acl_find_uid(acl_t acl, uid_t uid, acl_entry_t *entry); +int search_acl_groups(char*** dst, const char* path, bool* belong); diff --git a/src/shared/ask-password-api.c b/src/shared/ask-password-api.c new file mode 100644 index 0000000000..4557155d45 --- /dev/null +++ b/src/shared/ask-password-api.c @@ -0,0 +1,575 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ +#include <stdbool.h> +#include <termios.h> +#include <unistd.h> +#include <sys/poll.h> +#include <sys/inotify.h> +#include <errno.h> +#include <fcntl.h> +#include <sys/socket.h> +#include <string.h> +#include <sys/un.h> +#include <stddef.h> +#include <sys/signalfd.h> + +#include "util.h" +#include "mkdir.h" +#include "strv.h" + +#include "ask-password-api.h" + +static void backspace_chars(int ttyfd, size_t p) { + + if (ttyfd < 0) + return; + + while (p > 0) { + p--; + + loop_write(ttyfd, "\b \b", 3, false); + } +} + +int ask_password_tty( + const char *message, + usec_t until, + const char *flag_file, + char **_passphrase) { + + struct termios old_termios, new_termios; + char passphrase[LINE_MAX]; + size_t p = 0; + int r, ttyfd = -1, notify = -1; + struct pollfd pollfd[2]; + bool reset_tty = false; + bool silent_mode = false; + bool dirty = false; + enum { + POLL_TTY, + POLL_INOTIFY + }; + + assert(message); + assert(_passphrase); + + if (flag_file) { + if ((notify = inotify_init1(IN_CLOEXEC|IN_NONBLOCK)) < 0) { + r = -errno; + goto finish; + } + + if (inotify_add_watch(notify, flag_file, IN_ATTRIB /* for the link count */) < 0) { + r = -errno; + goto finish; + } + } + + if ((ttyfd = open("/dev/tty", O_RDWR|O_NOCTTY|O_CLOEXEC)) >= 0) { + + if (tcgetattr(ttyfd, &old_termios) < 0) { + r = -errno; + goto finish; + } + + loop_write(ttyfd, ANSI_HIGHLIGHT_ON, sizeof(ANSI_HIGHLIGHT_ON)-1, false); + loop_write(ttyfd, message, strlen(message), false); + loop_write(ttyfd, " ", 1, false); + loop_write(ttyfd, ANSI_HIGHLIGHT_OFF, sizeof(ANSI_HIGHLIGHT_OFF)-1, false); + + new_termios = old_termios; + new_termios.c_lflag &= ~(ICANON|ECHO); + new_termios.c_cc[VMIN] = 1; + new_termios.c_cc[VTIME] = 0; + + if (tcsetattr(ttyfd, TCSADRAIN, &new_termios) < 0) { + r = -errno; + goto finish; + } + + reset_tty = true; + } + + zero(pollfd); + pollfd[POLL_TTY].fd = ttyfd >= 0 ? ttyfd : STDIN_FILENO; + pollfd[POLL_TTY].events = POLLIN; + pollfd[POLL_INOTIFY].fd = notify; + pollfd[POLL_INOTIFY].events = POLLIN; + + for (;;) { + char c; + int sleep_for = -1, k; + ssize_t n; + + if (until > 0) { + usec_t y; + + y = now(CLOCK_MONOTONIC); + + if (y > until) { + r = -ETIME; + goto finish; + } + + sleep_for = (int) ((until - y) / USEC_PER_MSEC); + } + + if (flag_file) + if (access(flag_file, F_OK) < 0) { + r = -errno; + goto finish; + } + + if ((k = poll(pollfd, notify > 0 ? 2 : 1, sleep_for)) < 0) { + + if (errno == EINTR) + continue; + + r = -errno; + goto finish; + } else if (k == 0) { + r = -ETIME; + goto finish; + } + + if (notify > 0 && pollfd[POLL_INOTIFY].revents != 0) + flush_fd(notify); + + if (pollfd[POLL_TTY].revents == 0) + continue; + + if ((n = read(ttyfd >= 0 ? ttyfd : STDIN_FILENO, &c, 1)) < 0) { + + if (errno == EINTR || errno == EAGAIN) + continue; + + r = -errno; + goto finish; + + } else if (n == 0) + break; + + if (c == '\n') + break; + else if (c == 21) { /* C-u */ + + if (!silent_mode) + backspace_chars(ttyfd, p); + p = 0; + + } else if (c == '\b' || c == 127) { + + if (p > 0) { + + if (!silent_mode) + backspace_chars(ttyfd, 1); + + p--; + } else if (!dirty && !silent_mode) { + + silent_mode = true; + + /* There are two ways to enter silent + * mode. Either by pressing backspace + * as first key (and only as first key), + * or ... */ + if (ttyfd >= 0) + loop_write(ttyfd, "(no echo) ", 10, false); + + } else if (ttyfd >= 0) + loop_write(ttyfd, "\a", 1, false); + + } else if (c == '\t' && !silent_mode) { + + backspace_chars(ttyfd, p); + silent_mode = true; + + /* ... or by pressing TAB at any time. */ + + if (ttyfd >= 0) + loop_write(ttyfd, "(no echo) ", 10, false); + } else { + passphrase[p++] = c; + + if (!silent_mode && ttyfd >= 0) + loop_write(ttyfd, "*", 1, false); + + dirty = true; + } + } + + passphrase[p] = 0; + + if (!(*_passphrase = strdup(passphrase))) { + r = -ENOMEM; + goto finish; + } + + r = 0; + +finish: + if (notify >= 0) + close_nointr_nofail(notify); + + if (ttyfd >= 0) { + + if (reset_tty) { + loop_write(ttyfd, "\n", 1, false); + tcsetattr(ttyfd, TCSADRAIN, &old_termios); + } + + close_nointr_nofail(ttyfd); + } + + return r; +} + +static int create_socket(char **name) { + int fd; + union { + struct sockaddr sa; + struct sockaddr_un un; + } sa = { + .un.sun_family = AF_UNIX, + }; + int one = 1, r; + char *c; + + assert(name); + + fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (fd < 0) { + log_error("socket() failed: %m"); + return -errno; + } + + snprintf(sa.un.sun_path, sizeof(sa.un.sun_path)-1, "/run/systemd/ask-password/sck.%llu", random_ull()); + + RUN_WITH_UMASK(0177) { + r = bind(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path)); + } + + if (r < 0) { + r = -errno; + log_error("bind() failed: %m"); + goto fail; + } + + if (setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one)) < 0) { + r = -errno; + log_error("SO_PASSCRED failed: %m"); + goto fail; + } + + c = strdup(sa.un.sun_path); + if (!c) { + r = log_oom(); + goto fail; + } + + *name = c; + return fd; + +fail: + close_nointr_nofail(fd); + + return r; +} + +int ask_password_agent( + const char *message, + const char *icon, + usec_t until, + bool accept_cached, + char ***_passphrases) { + + enum { + FD_SOCKET, + FD_SIGNAL, + _FD_MAX + }; + + char temp[] = "/run/systemd/ask-password/tmp.XXXXXX"; + char final[sizeof(temp)] = ""; + int fd = -1, r; + FILE *f = NULL; + char *socket_name = NULL; + int socket_fd = -1, signal_fd = -1; + sigset_t mask, oldmask; + struct pollfd pollfd[_FD_MAX]; + + assert(_passphrases); + + assert_se(sigemptyset(&mask) == 0); + sigset_add_many(&mask, SIGINT, SIGTERM, -1); + assert_se(sigprocmask(SIG_BLOCK, &mask, &oldmask) == 0); + + mkdir_p_label("/run/systemd/ask-password", 0755); + + RUN_WITH_UMASK(0022) { + fd = mkostemp(temp, O_CLOEXEC|O_CREAT|O_WRONLY); + } + + if (fd < 0) { + log_error("Failed to create password file: %m"); + r = -errno; + goto finish; + } + + fchmod(fd, 0644); + + if (!(f = fdopen(fd, "w"))) { + log_error("Failed to allocate FILE: %m"); + r = -errno; + goto finish; + } + + fd = -1; + + if ((signal_fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC)) < 0) { + log_error("signalfd(): %m"); + r = -errno; + goto finish; + } + + if ((socket_fd = create_socket(&socket_name)) < 0) { + r = socket_fd; + goto finish; + } + + fprintf(f, + "[Ask]\n" + "PID=%lu\n" + "Socket=%s\n" + "AcceptCached=%i\n" + "NotAfter=%llu\n", + (unsigned long) getpid(), + socket_name, + accept_cached ? 1 : 0, + (unsigned long long) until); + + if (message) + fprintf(f, "Message=%s\n", message); + + if (icon) + fprintf(f, "Icon=%s\n", icon); + + fflush(f); + + if (ferror(f)) { + log_error("Failed to write query file: %m"); + r = -errno; + goto finish; + } + + memcpy(final, temp, sizeof(temp)); + + final[sizeof(final)-11] = 'a'; + final[sizeof(final)-10] = 's'; + final[sizeof(final)-9] = 'k'; + + if (rename(temp, final) < 0) { + log_error("Failed to rename query file: %m"); + r = -errno; + goto finish; + } + + zero(pollfd); + pollfd[FD_SOCKET].fd = socket_fd; + pollfd[FD_SOCKET].events = POLLIN; + pollfd[FD_SIGNAL].fd = signal_fd; + pollfd[FD_SIGNAL].events = POLLIN; + + for (;;) { + char passphrase[LINE_MAX+1]; + struct msghdr msghdr; + struct iovec iovec; + struct ucred *ucred; + union { + struct cmsghdr cmsghdr; + uint8_t buf[CMSG_SPACE(sizeof(struct ucred))]; + } control; + ssize_t n; + int k; + usec_t t; + + t = now(CLOCK_MONOTONIC); + + if (until > 0 && until <= t) { + log_notice("Timed out"); + r = -ETIME; + goto finish; + } + + if ((k = poll(pollfd, _FD_MAX, until > 0 ? (int) ((until-t)/USEC_PER_MSEC) : -1)) < 0) { + + if (errno == EINTR) + continue; + + log_error("poll() failed: %m"); + r = -errno; + goto finish; + } + + if (k <= 0) { + log_notice("Timed out"); + r = -ETIME; + goto finish; + } + + if (pollfd[FD_SIGNAL].revents & POLLIN) { + r = -EINTR; + goto finish; + } + + if (pollfd[FD_SOCKET].revents != POLLIN) { + log_error("Unexpected poll() event."); + r = -EIO; + goto finish; + } + + zero(iovec); + iovec.iov_base = passphrase; + iovec.iov_len = sizeof(passphrase); + + zero(control); + zero(msghdr); + msghdr.msg_iov = &iovec; + msghdr.msg_iovlen = 1; + msghdr.msg_control = &control; + msghdr.msg_controllen = sizeof(control); + + if ((n = recvmsg(socket_fd, &msghdr, 0)) < 0) { + + if (errno == EAGAIN || + errno == EINTR) + continue; + + log_error("recvmsg() failed: %m"); + r = -errno; + goto finish; + } + + if (n <= 0) { + log_error("Message too short"); + continue; + } + + if (msghdr.msg_controllen < CMSG_LEN(sizeof(struct ucred)) || + control.cmsghdr.cmsg_level != SOL_SOCKET || + control.cmsghdr.cmsg_type != SCM_CREDENTIALS || + control.cmsghdr.cmsg_len != CMSG_LEN(sizeof(struct ucred))) { + log_warning("Received message without credentials. Ignoring."); + continue; + } + + ucred = (struct ucred*) CMSG_DATA(&control.cmsghdr); + if (ucred->uid != 0) { + log_warning("Got request from unprivileged user. Ignoring."); + continue; + } + + if (passphrase[0] == '+') { + char **l; + + if (n == 1) + l = strv_new("", NULL); + else + l = strv_parse_nulstr(passphrase+1, n-1); + /* An empty message refers to the empty password */ + + if (!l) { + r = -ENOMEM; + goto finish; + } + + if (strv_length(l) <= 0) { + strv_free(l); + log_error("Invalid packet"); + continue; + } + + *_passphrases = l; + + } else if (passphrase[0] == '-') { + r = -ECANCELED; + goto finish; + } else { + log_error("Invalid packet"); + continue; + } + + break; + } + + r = 0; + +finish: + if (fd >= 0) + close_nointr_nofail(fd); + + if (socket_name) { + unlink(socket_name); + free(socket_name); + } + + if (socket_fd >= 0) + close_nointr_nofail(socket_fd); + + if (signal_fd >= 0) + close_nointr_nofail(signal_fd); + + if (f) + fclose(f); + + unlink(temp); + + if (final[0]) + unlink(final); + + assert_se(sigprocmask(SIG_SETMASK, &oldmask, NULL) == 0); + + return r; +} + +int ask_password_auto(const char *message, const char *icon, usec_t until, bool accept_cached, char ***_passphrases) { + assert(message); + assert(_passphrases); + + if (isatty(STDIN_FILENO)) { + int r; + char *s = NULL, **l = NULL; + + if ((r = ask_password_tty(message, until, NULL, &s)) < 0) + return r; + + l = strv_new(s, NULL); + free(s); + + if (!l) + return -ENOMEM; + + *_passphrases = l; + return r; + + } else + return ask_password_agent(message, icon, until, accept_cached, _passphrases); +} diff --git a/src/shared/ask-password-api.h b/src/shared/ask-password-api.h new file mode 100644 index 0000000000..288a0f48cf --- /dev/null +++ b/src/shared/ask-password-api.h @@ -0,0 +1,30 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "util.h" + +int ask_password_tty(const char *message, usec_t until, const char *flag_file, char **_passphrase); + +int ask_password_agent(const char *message, const char *icon, usec_t until, bool accept_cached, char ***_passphrases); + +int ask_password_auto(const char *message, const char *icon, usec_t until, bool accept_cached, char ***_passphrases); diff --git a/src/shared/audit.c b/src/shared/audit.c new file mode 100644 index 0000000000..97560cc9a3 --- /dev/null +++ b/src/shared/audit.c @@ -0,0 +1,128 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <assert.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <stdlib.h> +#include <stdio.h> +#include <ctype.h> +#include <sys/prctl.h> +#include <sys/capability.h> + +#include "macro.h" +#include "audit.h" +#include "util.h" +#include "log.h" +#include "fileio.h" +#include "virt.h" + +int audit_session_from_pid(pid_t pid, uint32_t *id) { + char *s; + uint32_t u; + int r; + + assert(id); + + if (have_effective_cap(CAP_AUDIT_CONTROL) <= 0) + return -ENOENT; + + /* Audit doesn't support containers right now */ + if (detect_container(NULL) > 0) + return -ENOTSUP; + + if (pid == 0) + r = read_one_line_file("/proc/self/sessionid", &s); + else { + char *p; + + if (asprintf(&p, "/proc/%lu/sessionid", (unsigned long) pid) < 0) + return -ENOMEM; + + r = read_one_line_file(p, &s); + free(p); + } + + if (r < 0) + return r; + + r = safe_atou32(s, &u); + free(s); + + if (r < 0) + return r; + + if (u == (uint32_t) -1 || u <= 0) + return -ENOENT; + + *id = u; + return 0; +} + +int audit_loginuid_from_pid(pid_t pid, uid_t *uid) { + char *s; + uid_t u; + int r; + + assert(uid); + + /* Only use audit login uid if we are executed with sufficient + * capabilities so that pam_loginuid could do its job. If we + * are lacking the CAP_AUDIT_CONTROL capabality we most likely + * are being run in a container and /proc/self/loginuid is + * useless since it probably contains a uid of the host + * system. */ + + if (have_effective_cap(CAP_AUDIT_CONTROL) <= 0) + return -ENOENT; + + /* Audit doesn't support containers right now */ + if (detect_container(NULL) > 0) + return -ENOTSUP; + + if (pid == 0) + r = read_one_line_file("/proc/self/loginuid", &s); + else { + char *p; + + if (asprintf(&p, "/proc/%lu/loginuid", (unsigned long) pid) < 0) + return -ENOMEM; + + r = read_one_line_file(p, &s); + free(p); + } + + if (r < 0) + return r; + + r = parse_uid(s, &u); + free(s); + + if (r < 0) + return r; + + if (u == (uid_t) -1) + return -ENOENT; + + *uid = (uid_t) u; + return 0; +} diff --git a/src/shared/audit.h b/src/shared/audit.h new file mode 100644 index 0000000000..490a0b0320 --- /dev/null +++ b/src/shared/audit.h @@ -0,0 +1,32 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef fooaudithfoo +#define fooaudithfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <sys/types.h> + +#include "capability.h" + +int audit_session_from_pid(pid_t pid, uint32_t *id); +int audit_loginuid_from_pid(pid_t pid, uid_t *uid); + +#endif diff --git a/src/shared/calendarspec.c b/src/shared/calendarspec.c new file mode 100644 index 0000000000..7979e2384f --- /dev/null +++ b/src/shared/calendarspec.c @@ -0,0 +1,927 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2012 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <stdlib.h> +#include <string.h> + +#include "calendarspec.h" + +static void free_chain(CalendarComponent *c) { + CalendarComponent *n; + + while (c) { + n = c->next; + free(c); + c = n; + } +} + +void calendar_spec_free(CalendarSpec *c) { + assert(c); + + free_chain(c->year); + free_chain(c->month); + free_chain(c->day); + free_chain(c->hour); + free_chain(c->minute); + free_chain(c->second); + + free(c); +} + +static int component_compare(const void *_a, const void *_b) { + CalendarComponent * const *a = _a, * const *b = _b; + + if ((*a)->value < (*b)->value) + return -1; + if ((*a)->value > (*b)->value) + return 1; + + if ((*a)->repeat < (*b)->repeat) + return -1; + if ((*a)->repeat > (*b)->repeat) + return 1; + + return 0; +} + +static void sort_chain(CalendarComponent **c) { + unsigned n = 0, k; + CalendarComponent **b, *i, **j, *next; + + assert(c); + + for (i = *c; i; i = i->next) + n++; + + if (n <= 1) + return; + + j = b = alloca(sizeof(CalendarComponent*) * n); + for (i = *c; i; i = i->next) + *(j++) = i; + + qsort(b, n, sizeof(CalendarComponent*), component_compare); + + b[n-1]->next = NULL; + next = b[n-1]; + + /* Drop non-unique entries */ + for (k = n-1; k > 0; k--) { + if (b[k-1]->value == next->value && + b[k-1]->repeat == next->repeat) { + free(b[k-1]); + continue; + } + + b[k-1]->next = next; + next = b[k-1]; + } + + *c = next; +} + +static void fix_year(CalendarComponent *c) { + /* Turns 12 → 2012, 89 → 1989 */ + + while(c) { + CalendarComponent *n = c->next; + + if (c->value >= 0 && c->value < 70) + c->value += 2000; + + if (c->value >= 70 && c->value < 100) + c->value += 1900; + + c = n; + } +} + +int calendar_spec_normalize(CalendarSpec *c) { + assert(c); + + if (c->weekdays_bits <= 0 || c->weekdays_bits >= 127) + c->weekdays_bits = -1; + + fix_year(c->year); + + sort_chain(&c->year); + sort_chain(&c->month); + sort_chain(&c->day); + sort_chain(&c->hour); + sort_chain(&c->minute); + sort_chain(&c->second); + + return 0; +} + +_pure_ static bool chain_valid(CalendarComponent *c, int from, int to) { + if (!c) + return true; + + if (c->value < from || c->value > to) + return false; + + if (c->value + c->repeat > to) + return false; + + if (c->next) + return chain_valid(c->next, from, to); + + return true; +} + +_pure_ bool calendar_spec_valid(CalendarSpec *c) { + assert(c); + + if (c->weekdays_bits > 127) + return false; + + if (!chain_valid(c->year, 1970, 2199)) + return false; + + if (!chain_valid(c->month, 1, 12)) + return false; + + if (!chain_valid(c->day, 1, 31)) + return false; + + if (!chain_valid(c->hour, 0, 23)) + return false; + + if (!chain_valid(c->minute, 0, 59)) + return false; + + if (!chain_valid(c->second, 0, 59)) + return false; + + return true; +} + +static void format_weekdays(FILE *f, const CalendarSpec *c) { + static const char *const days[] = { + "Mon", + "Tue", + "Wed", + "Thu", + "Fri", + "Sat", + "Sun" + }; + + int l, x; + bool need_colon = false; + + assert(f); + assert(c); + assert(c->weekdays_bits > 0 && c->weekdays_bits <= 127); + + for (x = 0, l = -1; x < (int) ELEMENTSOF(days); x++) { + + if (c->weekdays_bits & (1 << x)) { + + if (l < 0) { + if (need_colon) + fputc(',', f); + else + need_colon = true; + + fputs(days[x], f); + l = x; + } + + } else if (l >= 0) { + + if (x > l + 1) { + fputc(x > l + 2 ? '-' : ',', f); + fputs(days[x-1], f); + } + + l = -1; + } + } + + if (l >= 0 && x > l + 1) { + fputc(x > l + 2 ? '-' : ',', f); + fputs(days[x-1], f); + } +} + +static void format_chain(FILE *f, int space, const CalendarComponent *c) { + assert(f); + + if (!c) { + fputc('*', f); + return; + } + + assert(c->value >= 0); + fprintf(f, "%0*i", space, c->value); + + if (c->repeat > 0) + fprintf(f, "/%i", c->repeat); + + if (c->next) { + fputc(',', f); + format_chain(f, space, c->next); + } +} + +int calendar_spec_to_string(const CalendarSpec *c, char **p) { + char *buf = NULL; + size_t sz = 0; + FILE *f; + + assert(c); + assert(p); + + f = open_memstream(&buf, &sz); + if (!f) + return -ENOMEM; + + if (c->weekdays_bits > 0 && c->weekdays_bits <= 127) { + format_weekdays(f, c); + fputc(' ', f); + } + + format_chain(f, 4, c->year); + fputc('-', f); + format_chain(f, 2, c->month); + fputc('-', f); + format_chain(f, 2, c->day); + fputc(' ', f); + format_chain(f, 2, c->hour); + fputc(':', f); + format_chain(f, 2, c->minute); + fputc(':', f); + format_chain(f, 2, c->second); + + fflush(f); + + if (ferror(f)) { + free(buf); + fclose(f); + return -ENOMEM; + } + + fclose(f); + + *p = buf; + return 0; +} + +static int parse_weekdays(const char **p, CalendarSpec *c) { + static const struct { + const char *name; + const int nr; + } day_nr[] = { + { "Monday", 0 }, + { "Mon", 0 }, + { "Tuesday", 1 }, + { "Tue", 1 }, + { "Wednesday", 2 }, + { "Wed", 2 }, + { "Thursday", 3 }, + { "Thu", 3 }, + { "Friday", 4 }, + { "Fri", 4 }, + { "Saturday", 5 }, + { "Sat", 5 }, + { "Sunday", 6 }, + { "Sun", 6 } + }; + + int l = -1; + bool first = true; + + assert(p); + assert(*p); + assert(c); + + for (;;) { + unsigned i; + + if (!first && **p == ' ') + return 0; + + for (i = 0; i < ELEMENTSOF(day_nr); i++) { + size_t skip; + + if (!startswith_no_case(*p, day_nr[i].name)) + continue; + + skip = strlen(day_nr[i].name); + + if ((*p)[skip] != '-' && + (*p)[skip] != ',' && + (*p)[skip] != ' ' && + (*p)[skip] != 0) + return -EINVAL; + + c->weekdays_bits |= 1 << day_nr[i].nr; + + if (l >= 0) { + int j; + + if (l > day_nr[i].nr) + return -EINVAL; + + for (j = l + 1; j < day_nr[i].nr; j++) + c->weekdays_bits |= 1 << j; + } + + *p += skip; + break; + } + + /* Couldn't find this prefix, so let's assume the + weekday was not specified and let's continue with + the date */ + if (i >= ELEMENTSOF(day_nr)) + return first ? 0 : -EINVAL; + + /* We reached the end of the string */ + if (**p == 0) + return 0; + + /* We reached the end of the weekday spec part */ + if (**p == ' ') { + *p += strspn(*p, " "); + return 0; + } + + if (**p == '-') { + if (l >= 0) + return -EINVAL; + + l = day_nr[i].nr; + } else + l = -1; + + *p += 1; + first = false; + } +} + +static int prepend_component(const char **p, CalendarComponent **c) { + unsigned long value, repeat = 0; + char *e = NULL, *ee = NULL; + CalendarComponent *cc; + + assert(p); + assert(c); + + errno = 0; + value = strtoul(*p, &e, 10); + if (errno > 0) + return -errno; + if (e == *p) + return -EINVAL; + if ((unsigned long) (int) value != value) + return -ERANGE; + + if (*e == '/') { + repeat = strtoul(e+1, &ee, 10); + if (errno > 0) + return -errno; + if (ee == e+1) + return -EINVAL; + if ((unsigned long) (int) repeat != repeat) + return -ERANGE; + if (repeat <= 0) + return -ERANGE; + + e = ee; + } + + if (*e != 0 && *e != ' ' && *e != ',' && *e != '-' && *e != ':') + return -EINVAL; + + cc = new0(CalendarComponent, 1); + if (!cc) + return -ENOMEM; + + cc->value = value; + cc->repeat = repeat; + cc->next = *c; + + *p = e; + *c = cc; + + if (*e ==',') { + *p += 1; + return prepend_component(p, c); + } + + return 0; +} + +static int parse_chain(const char **p, CalendarComponent **c) { + const char *t; + CalendarComponent *cc = NULL; + int r; + + assert(p); + assert(c); + + t = *p; + + if (t[0] == '*') { + *p = t + 1; + *c = NULL; + return 0; + } + + r = prepend_component(&t, &cc); + if (r < 0) { + free_chain(cc); + return r; + } + + *p = t; + *c = cc; + return 0; +} + +static int const_chain(int value, CalendarComponent **c) { + CalendarComponent *cc = NULL; + + assert(c); + + cc = new0(CalendarComponent, 1); + if (!cc) + return -ENOMEM; + + cc->value = value; + cc->repeat = 0; + cc->next = NULL; + + *c = cc; + + return 0; +} + +static int parse_date(const char **p, CalendarSpec *c) { + const char *t; + int r; + CalendarComponent *first, *second, *third; + + assert(p); + assert(*p); + assert(c); + + t = *p; + + if (*t == 0) + return 0; + + r = parse_chain(&t, &first); + if (r < 0) + return r; + + /* Already the end? A ':' as separator? In that case this was a time, not a date */ + if (*t == 0 || *t == ':') { + free_chain(first); + return 0; + } + + if (*t != '-') { + free_chain(first); + return -EINVAL; + } + + t++; + r = parse_chain(&t, &second); + if (r < 0) { + free_chain(first); + return r; + } + + /* Got two parts, hence it's month and day */ + if (*t == ' ' || *t == 0) { + *p = t + strspn(t, " "); + c->month = first; + c->day = second; + return 0; + } + + if (*t != '-') { + free_chain(first); + free_chain(second); + return -EINVAL; + } + + t++; + r = parse_chain(&t, &third); + if (r < 0) { + free_chain(first); + free_chain(second); + return r; + } + + /* Got tree parts, hence it is year, month and day */ + if (*t == ' ' || *t == 0) { + *p = t + strspn(t, " "); + c->year = first; + c->month = second; + c->day = third; + return 0; + } + + free_chain(first); + free_chain(second); + free_chain(third); + return -EINVAL; +} + +static int parse_time(const char **p, CalendarSpec *c) { + CalendarComponent *h = NULL, *m = NULL, *s = NULL; + const char *t; + int r; + + assert(p); + assert(*p); + assert(c); + + t = *p; + + if (*t == 0) { + /* If no time is specified at all, but a date of some + * kind, then this means 00:00:00 */ + if (c->day || c->weekdays_bits > 0) + goto null_hour; + + goto finish; + } + + r = parse_chain(&t, &h); + if (r < 0) + goto fail; + + if (*t != ':') { + r = -EINVAL; + goto fail; + } + + t++; + r = parse_chain(&t, &m); + if (r < 0) + goto fail; + + /* Already at the end? Then it's hours and minutes, and seconds are 0 */ + if (*t == 0) { + if (m != NULL) + goto null_second; + + goto finish; + } + + if (*t != ':') { + r = -EINVAL; + goto fail; + } + + t++; + r = parse_chain(&t, &s); + if (r < 0) + goto fail; + + /* At the end? Then it's hours, minutes and seconds */ + if (*t == 0) + goto finish; + + r = -EINVAL; + goto fail; + +null_hour: + r = const_chain(0, &h); + if (r < 0) + goto fail; + + r = const_chain(0, &m); + if (r < 0) + goto fail; + +null_second: + r = const_chain(0, &s); + if (r < 0) + goto fail; + +finish: + *p = t; + c->hour = h; + c->minute = m; + c->second = s; + return 0; + +fail: + free_chain(h); + free_chain(m); + free_chain(s); + return r; +} + +int calendar_spec_from_string(const char *p, CalendarSpec **spec) { + CalendarSpec *c; + int r; + + assert(p); + assert(spec); + + if (isempty(p)) + return -EINVAL; + + c = new0(CalendarSpec, 1); + if (!c) + return -ENOMEM; + + if (strcaseeq(p, "hourly")) { + r = const_chain(0, &c->minute); + if (r < 0) + goto fail; + r = const_chain(0, &c->second); + if (r < 0) + goto fail; + + } else if (strcaseeq(p, "daily")) { + r = const_chain(0, &c->hour); + if (r < 0) + goto fail; + r = const_chain(0, &c->minute); + if (r < 0) + goto fail; + r = const_chain(0, &c->second); + if (r < 0) + goto fail; + + } else if (strcaseeq(p, "monthly")) { + r = const_chain(1, &c->day); + if (r < 0) + goto fail; + r = const_chain(0, &c->hour); + if (r < 0) + goto fail; + r = const_chain(0, &c->minute); + if (r < 0) + goto fail; + r = const_chain(0, &c->second); + if (r < 0) + goto fail; + + } else if (strcaseeq(p, "weekly")) { + + c->weekdays_bits = 1; + + r = const_chain(0, &c->hour); + if (r < 0) + goto fail; + r = const_chain(0, &c->minute); + if (r < 0) + goto fail; + r = const_chain(0, &c->second); + if (r < 0) + goto fail; + + } else { + r = parse_weekdays(&p, c); + if (r < 0) + goto fail; + + r = parse_date(&p, c); + if (r < 0) + goto fail; + + r = parse_time(&p, c); + if (r < 0) + goto fail; + + if (*p != 0) { + r = -EINVAL; + goto fail; + } + } + + r = calendar_spec_normalize(c); + if (r < 0) + goto fail; + + if (!calendar_spec_valid(c)) { + r = -EINVAL; + goto fail; + } + + *spec = c; + return 0; + +fail: + calendar_spec_free(c); + return r; +} + +static int find_matching_component(const CalendarComponent *c, int *val) { + const CalendarComponent *n; + int d = -1; + bool d_set = false; + int r; + + assert(val); + + if (!c) + return 0; + + while (c) { + n = c->next; + + if (c->value >= *val) { + + if (!d_set || c->value < d) { + d = c->value; + d_set = true; + } + + } else if (c->repeat > 0) { + int k; + + k = c->value + c->repeat * ((*val - c->value + c->repeat -1) / c->repeat); + + if (!d_set || k < d) { + d = k; + d_set = true; + } + } + + c = n; + } + + if (!d_set) + return -ENOENT; + + r = *val != d; + *val = d; + return r; +} + +static bool tm_out_of_bounds(const struct tm *tm) { + struct tm t; + assert(tm); + + t = *tm; + + if (mktime(&t) == (time_t) -1) + return true; + + /* Did any normalization take place? If so, it was out of bounds before */ + return + t.tm_year != tm->tm_year || + t.tm_mon != tm->tm_mon || + t.tm_mday != tm->tm_mday || + t.tm_hour != tm->tm_hour || + t.tm_min != tm->tm_min || + t.tm_sec != tm->tm_sec; +} + +static bool matches_weekday(int weekdays_bits, const struct tm *tm) { + struct tm t; + int k; + + if (weekdays_bits < 0 || weekdays_bits >= 127) + return true; + + t = *tm; + if (mktime(&t) == (time_t) -1) + return false; + + k = t.tm_wday == 0 ? 6 : t.tm_wday - 1; + return (weekdays_bits & (1 << k)); +} + +static int find_next(const CalendarSpec *spec, struct tm *tm) { + struct tm c; + int r; + + assert(spec); + assert(tm); + + c = *tm; + + for (;;) { + /* Normalize the current date */ + mktime(&c); + c.tm_isdst = -1; + + c.tm_year += 1900; + r = find_matching_component(spec->year, &c.tm_year); + c.tm_year -= 1900; + + if (r > 0) { + c.tm_mon = 0; + c.tm_mday = 1; + c.tm_hour = c.tm_min = c.tm_sec = 0; + } + if (r < 0 || tm_out_of_bounds(&c)) + return r; + + c.tm_mon += 1; + r = find_matching_component(spec->month, &c.tm_mon); + c.tm_mon -= 1; + + if (r > 0) { + c.tm_mday = 1; + c.tm_hour = c.tm_min = c.tm_sec = 0; + } + if (r < 0 || tm_out_of_bounds(&c)) { + c.tm_year ++; + c.tm_mon = 0; + c.tm_mday = 1; + c.tm_hour = c.tm_min = c.tm_sec = 0; + continue; + } + + r = find_matching_component(spec->day, &c.tm_mday); + if (r > 0) + c.tm_hour = c.tm_min = c.tm_sec = 0; + if (r < 0 || tm_out_of_bounds(&c)) { + c.tm_mon ++; + c.tm_mday = 1; + c.tm_hour = c.tm_min = c.tm_sec = 0; + continue; + } + + if (!matches_weekday(spec->weekdays_bits, &c)) { + c.tm_mday++; + c.tm_hour = c.tm_min = c.tm_sec = 0; + continue; + } + + r = find_matching_component(spec->hour, &c.tm_hour); + if (r > 0) + c.tm_min = c.tm_sec = 0; + if (r < 0 || tm_out_of_bounds(&c)) { + c.tm_mday ++; + c.tm_hour = c.tm_min = c.tm_sec = 0; + continue; + } + + r = find_matching_component(spec->minute, &c.tm_min); + if (r > 0) + c.tm_sec = 0; + if (r < 0 || tm_out_of_bounds(&c)) { + c.tm_hour ++; + c.tm_min = c.tm_sec = 0; + continue; + } + + r = find_matching_component(spec->second, &c.tm_sec); + if (r < 0 || tm_out_of_bounds(&c)) { + c.tm_min ++; + c.tm_sec = 0; + continue; + } + + + *tm = c; + return 0; + } +} + +int calendar_spec_next_usec(const CalendarSpec *spec, usec_t usec, usec_t *next) { + struct tm tm; + time_t t; + int r; + + assert(spec); + assert(next); + + t = (time_t) (usec / USEC_PER_SEC) + 1; + assert_se(localtime_r(&t, &tm)); + + r = find_next(spec, &tm); + if (r < 0) + return r; + + t = mktime(&tm); + if (t == (time_t) -1) + return -EINVAL; + + + *next = (usec_t) t * USEC_PER_SEC; + return 0; +} diff --git a/src/shared/calendarspec.h b/src/shared/calendarspec.h new file mode 100644 index 0000000000..7baf318249 --- /dev/null +++ b/src/shared/calendarspec.h @@ -0,0 +1,57 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2012 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +/* A structure for specifying (possibly repetitive) points in calendar + * time, a la cron */ + +#include <stdbool.h> +#include "util.h" + +typedef struct CalendarComponent { + int value; + int repeat; + + struct CalendarComponent *next; +} CalendarComponent; + +typedef struct CalendarSpec { + int weekdays_bits; + + CalendarComponent *year; + CalendarComponent *month; + CalendarComponent *day; + + CalendarComponent *hour; + CalendarComponent *minute; + CalendarComponent *second; +} CalendarSpec; + +void calendar_spec_free(CalendarSpec *c); + +int calendar_spec_normalize(CalendarSpec *spec); +bool calendar_spec_valid(CalendarSpec *spec); + +int calendar_spec_to_string(const CalendarSpec *spec, char **p); +int calendar_spec_from_string(const char *p, CalendarSpec **spec); + +int calendar_spec_next_usec(const CalendarSpec *spec, usec_t usec, usec_t *next); diff --git a/src/shared/capability.c b/src/shared/capability.c new file mode 100644 index 0000000000..321952067d --- /dev/null +++ b/src/shared/capability.c @@ -0,0 +1,225 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <assert.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <stdio.h> +#include <sys/types.h> +#include <stdarg.h> +#include <ctype.h> +#include <sys/capability.h> +#include <sys/prctl.h> + +#include "macro.h" +#include "capability.h" +#include "util.h" +#include "log.h" +#include "fileio.h" + +int have_effective_cap(int value) { + cap_t cap; + cap_flag_value_t fv; + int r; + + cap = cap_get_proc(); + if (!cap) + return -errno; + + if (cap_get_flag(cap, value, CAP_EFFECTIVE, &fv) < 0) + r = -errno; + else + r = fv == CAP_SET; + + cap_free(cap); + return r; +} + +unsigned long cap_last_cap(void) { + static __thread unsigned long saved; + static __thread bool valid = false; + unsigned long p; + + if (valid) + return saved; + + p = (unsigned long) CAP_LAST_CAP; + + if (prctl(PR_CAPBSET_READ, p) < 0) { + + /* Hmm, look downwards, until we find one that + * works */ + for (p--; p > 0; p --) + if (prctl(PR_CAPBSET_READ, p) >= 0) + break; + + } else { + + /* Hmm, look upwards, until we find one that doesn't + * work */ + for (;; p++) + if (prctl(PR_CAPBSET_READ, p+1) < 0) + break; + } + + saved = p; + valid = true; + + return p; +} + +int capability_bounding_set_drop(uint64_t drop, bool right_now) { + unsigned long i; + cap_t after_cap = NULL, temp_cap = NULL; + cap_flag_value_t fv; + int r; + + /* If we are run as PID 1 we will lack CAP_SETPCAP by default + * in the effective set (yes, the kernel drops that when + * executing init!), so get it back temporarily so that we can + * call PR_CAPBSET_DROP. */ + + after_cap = cap_get_proc(); + if (!after_cap) + return -errno; + + if (cap_get_flag(after_cap, CAP_SETPCAP, CAP_EFFECTIVE, &fv) < 0) { + cap_free(after_cap); + return -errno; + } + + if (fv != CAP_SET) { + static const cap_value_t v = CAP_SETPCAP; + + temp_cap = cap_dup(after_cap); + if (!temp_cap) { + r = -errno; + goto finish; + } + + if (cap_set_flag(temp_cap, CAP_EFFECTIVE, 1, &v, CAP_SET) < 0) { + r = -errno; + goto finish; + } + + if (cap_set_proc(temp_cap) < 0) { + r = -errno; + goto finish; + } + } + + for (i = 0; i <= cap_last_cap(); i++) { + + if (drop & ((uint64_t) 1ULL << (uint64_t) i)) { + cap_value_t v; + + /* Drop it from the bounding set */ + if (prctl(PR_CAPBSET_DROP, i) < 0) { + r = -errno; + goto finish; + } + v = i; + + /* Also drop it from the inheritable set, so + * that anything we exec() loses the + * capability for good. */ + if (cap_set_flag(after_cap, CAP_INHERITABLE, 1, &v, CAP_CLEAR) < 0) { + r = -errno; + goto finish; + } + + /* If we shall apply this right now drop it + * also from our own capability sets. */ + if (right_now) { + if (cap_set_flag(after_cap, CAP_PERMITTED, 1, &v, CAP_CLEAR) < 0 || + cap_set_flag(after_cap, CAP_EFFECTIVE, 1, &v, CAP_CLEAR) < 0) { + r = -errno; + goto finish; + } + } + } + } + + r = 0; + +finish: + if (temp_cap) + cap_free(temp_cap); + + if (after_cap) { + cap_set_proc(after_cap); + cap_free(after_cap); + } + + return r; +} + +static int drop_from_file(const char *fn, uint64_t drop) { + int r, k; + uint32_t hi, lo; + uint64_t current, after; + char *p; + + r = read_one_line_file(fn, &p); + if (r < 0) + return r; + + assert_cc(sizeof(hi) == sizeof(unsigned)); + assert_cc(sizeof(lo) == sizeof(unsigned)); + + k = sscanf(p, "%u %u", &lo, &hi); + free(p); + + if (k != 2) + return -EIO; + + current = (uint64_t) lo | ((uint64_t) hi << 32ULL); + after = current & ~drop; + + if (current == after) + return 0; + + lo = (unsigned) (after & 0xFFFFFFFFULL); + hi = (unsigned) ((after >> 32ULL) & 0xFFFFFFFFULL); + + if (asprintf(&p, "%u %u", lo, hi) < 0) + return -ENOMEM; + + r = write_string_file(fn, p); + free(p); + + return r; +} + +int capability_bounding_set_drop_usermode(uint64_t drop) { + int r; + + r = drop_from_file("/proc/sys/kernel/usermodehelper/inheritable", drop); + if (r < 0) + return r; + + r = drop_from_file("/proc/sys/kernel/usermodehelper/bset", drop); + if (r < 0) + return r; + + return r; +} diff --git a/src/shared/capability.h b/src/shared/capability.h new file mode 100644 index 0000000000..6cb31bb510 --- /dev/null +++ b/src/shared/capability.h @@ -0,0 +1,33 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foocapabilityhfoo +#define foocapabilityhfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <inttypes.h> +#include <stdbool.h> + +unsigned long cap_last_cap(void); +int have_effective_cap(int value); +int capability_bounding_set_drop(uint64_t drop, bool right_now); +int capability_bounding_set_drop_usermode(uint64_t drop); + +#endif diff --git a/src/shared/cgroup-label.c b/src/shared/cgroup-label.c new file mode 100644 index 0000000000..5b5163c250 --- /dev/null +++ b/src/shared/cgroup-label.c @@ -0,0 +1,77 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <errno.h> +#include <unistd.h> +#include <signal.h> +#include <string.h> +#include <stdlib.h> +#include <dirent.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <ftw.h> + +#include "cgroup-util.h" +#include "log.h" +#include "set.h" +#include "macro.h" +#include "util.h" +#include "mkdir.h" + +int cg_create(const char *controller, const char *path, const char *suffix) { + _cleanup_free_ char *fs = NULL; + int r; + + r = cg_get_path_and_check(controller, path, suffix, &fs); + if (r < 0) + return r; + + r = mkdir_parents_label(fs, 0755); + if (r < 0) + return r; + + if (mkdir(fs, 0755) < 0) { + + if (errno == EEXIST) + return 0; + + return -errno; + } + + return 1; +} + +int cg_create_and_attach(const char *controller, const char *path, pid_t pid) { + int r, q; + + assert(pid >= 0); + + r = cg_create(controller, path, NULL); + if (r < 0) + return r; + + q = cg_attach(controller, path, pid); + if (q < 0) + return q; + + /* This does not remove the cgroup on failure */ + return r; +} diff --git a/src/shared/cgroup-show.c b/src/shared/cgroup-show.c new file mode 100644 index 0000000000..83cc0731b8 --- /dev/null +++ b/src/shared/cgroup-show.c @@ -0,0 +1,300 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <stdio.h> +#include <string.h> +#include <dirent.h> +#include <errno.h> + +#include "util.h" +#include "macro.h" +#include "path-util.h" +#include "cgroup-util.h" +#include "cgroup-show.h" + +static int compare(const void *a, const void *b) { + const pid_t *p = a, *q = b; + + if (*p < *q) + return -1; + if (*p > *q) + return 1; + return 0; +} + +static void show_pid_array(int pids[], unsigned n_pids, const char *prefix, unsigned n_columns, bool extra, bool more, bool kernel_threads, OutputFlags flags) { + unsigned i, m, pid_width; + pid_t biggest = 0; + + /* Filter duplicates */ + m = 0; + for (i = 0; i < n_pids; i++) { + unsigned j; + + if (pids[i] > biggest) + biggest = pids[i]; + + for (j = i+1; j < n_pids; j++) + if (pids[i] == pids[j]) + break; + + if (j >= n_pids) + pids[m++] = pids[i]; + } + n_pids = m; + pid_width = DECIMAL_STR_WIDTH(biggest); + + /* And sort */ + qsort(pids, n_pids, sizeof(pid_t), compare); + + if(flags & OUTPUT_FULL_WIDTH) + n_columns = 0; + else { + if (n_columns > pid_width+2) + n_columns -= pid_width+2; + else + n_columns = 20; + } + for (i = 0; i < n_pids; i++) { + char *t = NULL; + + get_process_cmdline(pids[i], n_columns, true, &t); + + printf("%s%s%*lu %s\n", + prefix, + draw_special_char(extra ? DRAW_TRIANGULAR_BULLET : + ((more || i < n_pids-1) ? DRAW_TREE_BRANCH : DRAW_TREE_RIGHT)), + pid_width, + (unsigned long) pids[i], + strna(t)); + + free(t); + } +} + + +static int show_cgroup_one_by_path(const char *path, const char *prefix, unsigned n_columns, bool more, bool kernel_threads, OutputFlags flags) { + char *fn; + _cleanup_fclose_ FILE *f = NULL; + size_t n = 0, n_allocated = 0; + _cleanup_free_ pid_t *pids = NULL; + char *p = NULL; + pid_t pid; + int r; + + r = cg_mangle_path(path, &p); + if (r < 0) + return r; + + fn = strappend(p, "/cgroup.procs"); + free(p); + if (!fn) + return -ENOMEM; + + f = fopen(fn, "re"); + free(fn); + if (!f) + return -errno; + + while ((r = cg_read_pid(f, &pid)) > 0) { + + if (!kernel_threads && is_kernel_thread(pid) > 0) + continue; + + if (n >= n_allocated) { + pid_t *npids; + + n_allocated = MAX(16U, n*2U); + + npids = realloc(pids, sizeof(pid_t) * n_allocated); + if (!npids) + return -ENOMEM; + + pids = npids; + } + + assert(n < n_allocated); + pids[n++] = pid; + } + + if (r < 0) + return r; + + if (n > 0) + show_pid_array(pids, n, prefix, n_columns, false, more, kernel_threads, flags); + + return 0; +} + +int show_cgroup_by_path(const char *path, const char *prefix, unsigned n_columns, bool kernel_threads, OutputFlags flags) { + _cleanup_free_ char *fn = NULL, *p1 = NULL, *last = NULL, *p2 = NULL; + _cleanup_closedir_ DIR *d = NULL; + char *gn = NULL; + bool shown_pids = false; + int r; + + assert(path); + + if (n_columns <= 0) + n_columns = columns(); + + if (!prefix) + prefix = ""; + + r = cg_mangle_path(path, &fn); + if (r < 0) + return r; + + d = opendir(fn); + if (!d) + return -errno; + + while ((r = cg_read_subgroup(d, &gn)) > 0) { + _cleanup_free_ char *k = NULL; + + k = strjoin(fn, "/", gn, NULL); + free(gn); + if (!k) + return -ENOMEM; + + if (!(flags & OUTPUT_SHOW_ALL) && cg_is_empty_recursive(NULL, k, false) > 0) + continue; + + if (!shown_pids) { + show_cgroup_one_by_path(path, prefix, n_columns, true, kernel_threads, flags); + shown_pids = true; + } + + if (last) { + printf("%s%s%s\n", prefix, draw_special_char(DRAW_TREE_BRANCH), + path_get_file_name(last)); + + if (!p1) { + p1 = strappend(prefix, draw_special_char(DRAW_TREE_VERT)); + if (!p1) + return -ENOMEM; + } + + show_cgroup_by_path(last, p1, n_columns-2, kernel_threads, flags); + free(last); + } + + last = k; + k = NULL; + } + + if (r < 0) + return r; + + if (!shown_pids) + show_cgroup_one_by_path(path, prefix, n_columns, !!last, kernel_threads, flags); + + if (last) { + printf("%s%s%s\n", prefix, draw_special_char(DRAW_TREE_RIGHT), + path_get_file_name(last)); + + if (!p2) { + p2 = strappend(prefix, " "); + if (!p2) + return -ENOMEM; + } + + show_cgroup_by_path(last, p2, n_columns-2, kernel_threads, flags); + } + + return 0; +} + +int show_cgroup(const char *controller, const char *path, const char *prefix, unsigned n_columns, bool kernel_threads, OutputFlags flags) { + _cleanup_free_ char *p = NULL; + int r; + + assert(path); + + r = cg_get_path(controller, path, NULL, &p); + if (r < 0) + return r; + + return show_cgroup_by_path(p, prefix, n_columns, kernel_threads, flags); +} + +static int show_extra_pids(const char *controller, const char *path, const char *prefix, unsigned n_columns, const pid_t pids[], unsigned n_pids, OutputFlags flags) { + _cleanup_free_ pid_t *copy = NULL; + unsigned i, j; + int r; + + assert(controller); + assert(path); + + if (n_pids <= 0) + return 0; + + if (n_columns <= 0) + n_columns = columns(); + + prefix = strempty(prefix); + + copy = new(pid_t, n_pids); + if (!copy) + return -ENOMEM; + + for (i = 0, j = 0; i < n_pids; i++) { + _cleanup_free_ char *k = NULL; + + r = cg_pid_get_path(controller, pids[i], &k); + if (r < 0) + return r; + + if (path_startswith(k, path)) + continue; + + copy[j++] = pids[i]; + } + + show_pid_array(copy, j, prefix, n_columns, true, false, false, flags); + + return 0; +} + +int show_cgroup_and_extra(const char *controller, const char *path, const char *prefix, unsigned n_columns, bool kernel_threads, const pid_t extra_pids[], unsigned n_extra_pids, OutputFlags flags) { + int r; + + assert(path); + + r = show_cgroup(controller, path, prefix, n_columns, kernel_threads, flags); + if (r < 0) + return r; + + return show_extra_pids(controller, path, prefix, n_columns, extra_pids, n_extra_pids, flags); +} + +int show_cgroup_and_extra_by_spec(const char *spec, const char *prefix, unsigned n_columns, bool kernel_threads, const pid_t extra_pids[], unsigned n_extra_pids, OutputFlags flags) { + _cleanup_free_ char *controller = NULL, *path = NULL; + int r; + + assert(spec); + + r = cg_split_spec(spec, &controller, &path); + if (r < 0) + return r; + + return show_cgroup_and_extra(controller, path, prefix, n_columns, kernel_threads, extra_pids, n_extra_pids, flags); +} diff --git a/src/shared/cgroup-show.h b/src/shared/cgroup-show.h new file mode 100644 index 0000000000..72bc8a650f --- /dev/null +++ b/src/shared/cgroup-show.h @@ -0,0 +1,36 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foocgroupshowhfoo +#define foocgroupshowhfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <stdbool.h> +#include <sys/types.h> +#include "util.h" +#include "logs-show.h" + +int show_cgroup_by_path(const char *path, const char *prefix, unsigned columns, bool kernel_threads, OutputFlags flags); +int show_cgroup(const char *controller, const char *path, const char *prefix, unsigned columns, bool kernel_threads, OutputFlags flags); + +int show_cgroup_and_extra_by_spec(const char *spec, const char *prefix, unsigned n_columns, bool kernel_threads, const pid_t extra_pids[], unsigned n_extra_pids, OutputFlags flags); +int show_cgroup_and_extra(const char *controller, const char *path, const char *prefix, unsigned n_columns, bool kernel_threads, const pid_t extra_pids[], unsigned n_extra_pids, OutputFlags flags); + +#endif diff --git a/src/shared/cgroup-util.c b/src/shared/cgroup-util.c new file mode 100644 index 0000000000..43c415d760 --- /dev/null +++ b/src/shared/cgroup-util.c @@ -0,0 +1,1603 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <errno.h> +#include <unistd.h> +#include <signal.h> +#include <string.h> +#include <stdlib.h> +#include <dirent.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <ftw.h> + +#include "cgroup-util.h" +#include "log.h" +#include "set.h" +#include "macro.h" +#include "util.h" +#include "path-util.h" +#include "strv.h" +#include "unit-name.h" +#include "fileio.h" + +int cg_enumerate_processes(const char *controller, const char *path, FILE **_f) { + _cleanup_free_ char *fs = NULL; + FILE *f; + int r; + + assert(_f); + + r = cg_get_path(controller, path, "cgroup.procs", &fs); + if (r < 0) + return r; + + f = fopen(fs, "re"); + if (!f) + return -errno; + + *_f = f; + return 0; +} + +int cg_enumerate_tasks(const char *controller, const char *path, FILE **_f) { + _cleanup_free_ char *fs = NULL; + FILE *f; + int r; + + assert(_f); + + r = cg_get_path(controller, path, "tasks", &fs); + if (r < 0) + return r; + + f = fopen(fs, "re"); + if (!f) + return -errno; + + *_f = f; + return 0; +} + +int cg_read_pid(FILE *f, pid_t *_pid) { + unsigned long ul; + + /* Note that the cgroup.procs might contain duplicates! See + * cgroups.txt for details. */ + + assert(f); + assert(_pid); + + errno = 0; + if (fscanf(f, "%lu", &ul) != 1) { + + if (feof(f)) + return 0; + + return errno ? -errno : -EIO; + } + + if (ul <= 0) + return -EIO; + + *_pid = (pid_t) ul; + return 1; +} + +int cg_enumerate_subgroups(const char *controller, const char *path, DIR **_d) { + _cleanup_free_ char *fs = NULL; + int r; + DIR *d; + + assert(_d); + + /* This is not recursive! */ + + r = cg_get_path(controller, path, NULL, &fs); + if (r < 0) + return r; + + d = opendir(fs); + if (!d) + return -errno; + + *_d = d; + return 0; +} + +int cg_read_subgroup(DIR *d, char **fn) { + struct dirent *de; + + assert(d); + assert(fn); + + FOREACH_DIRENT(de, d, return -errno) { + char *b; + + if (de->d_type != DT_DIR) + continue; + + if (streq(de->d_name, ".") || + streq(de->d_name, "..")) + continue; + + b = strdup(de->d_name); + if (!b) + return -ENOMEM; + + *fn = b; + return 1; + } + + return 0; +} + +int cg_rmdir(const char *controller, const char *path, bool honour_sticky) { + _cleanup_free_ char *p = NULL; + int r; + + r = cg_get_path(controller, path, NULL, &p); + if (r < 0) + return r; + + if (honour_sticky) { + char *tasks; + + /* If the sticky bit is set don't remove the directory */ + + tasks = strappend(p, "/tasks"); + if (!tasks) + return -ENOMEM; + + r = file_is_priv_sticky(tasks); + free(tasks); + + if (r > 0) + return 0; + } + + r = rmdir(p); + if (r < 0 && errno != ENOENT) + return -errno; + + return 0; +} + +int cg_kill(const char *controller, const char *path, int sig, bool sigcont, bool ignore_self, Set *s) { + _cleanup_set_free_ Set *allocated_set = NULL; + bool done = false; + int r, ret = 0; + pid_t my_pid; + + assert(sig >= 0); + + /* This goes through the tasks list and kills them all. This + * is repeated until no further processes are added to the + * tasks list, to properly handle forking processes */ + + if (!s) { + s = allocated_set = set_new(trivial_hash_func, trivial_compare_func); + if (!s) + return -ENOMEM; + } + + my_pid = getpid(); + + do { + _cleanup_fclose_ FILE *f = NULL; + pid_t pid = 0; + done = true; + + r = cg_enumerate_processes(controller, path, &f); + if (r < 0) { + if (ret >= 0 && r != -ENOENT) + return r; + + return ret; + } + + while ((r = cg_read_pid(f, &pid)) > 0) { + + if (ignore_self && pid == my_pid) + continue; + + if (set_get(s, LONG_TO_PTR(pid)) == LONG_TO_PTR(pid)) + continue; + + /* If we haven't killed this process yet, kill + * it */ + if (kill(pid, sig) < 0) { + if (ret >= 0 && errno != ESRCH) + ret = -errno; + } else if (ret == 0) { + + if (sigcont) + kill(pid, SIGCONT); + + ret = 1; + } + + done = false; + + r = set_put(s, LONG_TO_PTR(pid)); + if (r < 0) { + if (ret >= 0) + return r; + + return ret; + } + } + + if (r < 0) { + if (ret >= 0) + return r; + + return ret; + } + + /* To avoid racing against processes which fork + * quicker than we can kill them we repeat this until + * no new pids need to be killed. */ + + } while (!done); + + return ret; +} + +int cg_kill_recursive(const char *controller, const char *path, int sig, bool sigcont, bool ignore_self, bool rem, Set *s) { + _cleanup_set_free_ Set *allocated_set = NULL; + _cleanup_closedir_ DIR *d = NULL; + int r, ret = 0; + char *fn; + + assert(path); + assert(sig >= 0); + + if (!s) { + s = allocated_set = set_new(trivial_hash_func, trivial_compare_func); + if (!s) + return -ENOMEM; + } + + ret = cg_kill(controller, path, sig, sigcont, ignore_self, s); + + r = cg_enumerate_subgroups(controller, path, &d); + if (r < 0) { + if (ret >= 0 && r != -ENOENT) + return r; + + return ret; + } + + while ((r = cg_read_subgroup(d, &fn)) > 0) { + _cleanup_free_ char *p = NULL; + + p = strjoin(path, "/", fn, NULL); + free(fn); + if (!p) + return -ENOMEM; + + r = cg_kill_recursive(controller, p, sig, sigcont, ignore_self, rem, s); + if (ret >= 0 && r != 0) + ret = r; + } + + if (ret >= 0 && r < 0) + ret = r; + + if (rem) { + r = cg_rmdir(controller, path, true); + if (r < 0 && ret >= 0 && r != -ENOENT && r != -EBUSY) + return r; + } + + return ret; +} + +int cg_kill_recursive_and_wait(const char *controller, const char *path, bool rem) { + unsigned i; + + assert(path); + + /* This safely kills all processes; first it sends a SIGTERM, + * then checks 8 times after 200ms whether the group is now + * empty, then kills everything that is left with SIGKILL and + * finally checks 5 times after 200ms each whether the group + * is finally empty. */ + + for (i = 0; i < 15; i++) { + int sig, r; + + if (i <= 0) + sig = SIGTERM; + else if (i == 9) + sig = SIGKILL; + else + sig = 0; + + r = cg_kill_recursive(controller, path, sig, true, true, rem, NULL); + if (r <= 0) + return r; + + usleep(200 * USEC_PER_MSEC); + } + + return 0; +} + +int cg_migrate(const char *cfrom, const char *pfrom, const char *cto, const char *pto, bool ignore_self) { + bool done = false; + _cleanup_set_free_ Set *s = NULL; + int r, ret = 0; + pid_t my_pid; + + assert(cfrom); + assert(pfrom); + assert(cto); + assert(pto); + + s = set_new(trivial_hash_func, trivial_compare_func); + if (!s) + return -ENOMEM; + + my_pid = getpid(); + + do { + _cleanup_fclose_ FILE *f = NULL; + pid_t pid = 0; + done = true; + + r = cg_enumerate_tasks(cfrom, pfrom, &f); + if (r < 0) { + if (ret >= 0 && r != -ENOENT) + return r; + + return ret; + } + + while ((r = cg_read_pid(f, &pid)) > 0) { + + /* This might do weird stuff if we aren't a + * single-threaded program. However, we + * luckily know we are not */ + if (ignore_self && pid == my_pid) + continue; + + if (set_get(s, LONG_TO_PTR(pid)) == LONG_TO_PTR(pid)) + continue; + + r = cg_attach(cto, pto, pid); + if (r < 0) { + if (ret >= 0 && r != -ESRCH) + ret = r; + } else if (ret == 0) + ret = 1; + + done = false; + + r = set_put(s, LONG_TO_PTR(pid)); + if (r < 0) { + if (ret >= 0) + return r; + + return ret; + } + } + + if (r < 0) { + if (ret >= 0) + return r; + + return ret; + } + } while (!done); + + return ret; +} + +int cg_migrate_recursive(const char *cfrom, const char *pfrom, const char *cto, const char *pto, bool ignore_self, bool rem) { + _cleanup_closedir_ DIR *d = NULL; + int r, ret = 0; + char *fn; + + assert(cfrom); + assert(pfrom); + assert(cto); + assert(pto); + + ret = cg_migrate(cfrom, pfrom, cto, pto, ignore_self); + + r = cg_enumerate_subgroups(cfrom, pfrom, &d); + if (r < 0) { + if (ret >= 0 && r != -ENOENT) + return r; + + return ret; + } + + while ((r = cg_read_subgroup(d, &fn)) > 0) { + _cleanup_free_ char *p = NULL; + + p = strjoin(pfrom, "/", fn, NULL); + free(fn); + if (!p) { + if (ret >= 0) + return -ENOMEM; + + return ret; + } + + r = cg_migrate_recursive(cfrom, p, cto, pto, ignore_self, rem); + if (r != 0 && ret >= 0) + ret = r; + } + + if (r < 0 && ret >= 0) + ret = r; + + if (rem) { + r = cg_rmdir(cfrom, pfrom, true); + if (r < 0 && ret >= 0 && r != -ENOENT && r != -EBUSY) + return r; + } + + return ret; +} + +static const char *normalize_controller(const char *controller) { + + assert(controller); + + if (streq(controller, SYSTEMD_CGROUP_CONTROLLER)) + return "systemd"; + else if (startswith(controller, "name=")) + return controller + 5; + else + return controller; +} + +static int join_path(const char *controller, const char *path, const char *suffix, char **fs) { + char *t = NULL; + + if (controller) { + if (path && suffix) + t = strjoin("/sys/fs/cgroup/", controller, "/", path, "/", suffix, NULL); + else if (path) + t = strjoin("/sys/fs/cgroup/", controller, "/", path, NULL); + else if (suffix) + t = strjoin("/sys/fs/cgroup/", controller, "/", suffix, NULL); + else + t = strappend("/sys/fs/cgroup/", controller); + } else { + if (path && suffix) + t = strjoin(path, "/", suffix, NULL); + else if (path) + t = strdup(path); + else + return -EINVAL; + } + + if (!t) + return -ENOMEM; + + path_kill_slashes(t); + + *fs = t; + return 0; +} + +int cg_get_path(const char *controller, const char *path, const char *suffix, char **fs) { + const char *p; + static __thread bool good = false; + + assert(fs); + + if (controller && !cg_controller_is_valid(controller, true)) + return -EINVAL; + + if (_unlikely_(!good)) { + int r; + + r = path_is_mount_point("/sys/fs/cgroup", false); + if (r <= 0) + return r < 0 ? r : -ENOENT; + + /* Cache this to save a few stat()s */ + good = true; + } + + p = controller ? normalize_controller(controller) : NULL; + + return join_path(p, path, suffix, fs); +} + +static int check_hierarchy(const char *p) { + char *cc; + + assert(p); + + /* Check if this controller actually really exists */ + cc = alloca(sizeof("/sys/fs/cgroup/") + strlen(p)); + strcpy(stpcpy(cc, "/sys/fs/cgroup/"), p); + if (access(cc, F_OK) < 0) + return -errno; + + return 0; +} + +int cg_get_path_and_check(const char *controller, const char *path, const char *suffix, char **fs) { + const char *p; + int r; + + assert(fs); + + if (!cg_controller_is_valid(controller, true)) + return -EINVAL; + + /* Normalize the controller syntax */ + p = normalize_controller(controller); + + /* Check if this controller actually really exists */ + r = check_hierarchy(p); + if (r < 0) + return r; + + return join_path(p, path, suffix, fs); +} + +static int trim_cb(const char *path, const struct stat *sb, int typeflag, struct FTW *ftwbuf) { + char *p; + bool is_sticky; + + if (typeflag != FTW_DP) + return 0; + + if (ftwbuf->level < 1) + return 0; + + p = strappend(path, "/tasks"); + if (!p) { + errno = ENOMEM; + return 1; + } + + is_sticky = file_is_priv_sticky(p) > 0; + free(p); + + if (is_sticky) + return 0; + + rmdir(path); + return 0; +} + +int cg_trim(const char *controller, const char *path, bool delete_root) { + _cleanup_free_ char *fs = NULL; + int r = 0; + + assert(path); + + r = cg_get_path(controller, path, NULL, &fs); + if (r < 0) + return r; + + errno = 0; + if (nftw(fs, trim_cb, 64, FTW_DEPTH|FTW_MOUNT|FTW_PHYS) != 0) + r = errno ? -errno : -EIO; + + if (delete_root) { + bool is_sticky; + char *p; + + p = strappend(fs, "/tasks"); + if (!p) + return -ENOMEM; + + is_sticky = file_is_priv_sticky(p) > 0; + free(p); + + if (!is_sticky) + if (rmdir(fs) < 0 && errno != ENOENT && r == 0) + return -errno; + } + + return r; +} + +int cg_delete(const char *controller, const char *path) { + _cleanup_free_ char *parent = NULL; + int r; + + assert(path); + + r = path_get_parent(path, &parent); + if (r < 0) + return r; + + r = cg_migrate_recursive(controller, path, controller, parent, false, true); + return r == -ENOENT ? 0 : r; +} + +int cg_attach(const char *controller, const char *path, pid_t pid) { + _cleanup_free_ char *fs = NULL; + char c[DECIMAL_STR_MAX(pid_t) + 2]; + int r; + + assert(path); + assert(pid >= 0); + + r = cg_get_path_and_check(controller, path, "tasks", &fs); + if (r < 0) + return r; + + if (pid == 0) + pid = getpid(); + + snprintf(c, sizeof(c), "%lu\n", (unsigned long) pid); + + return write_string_file(fs, c); +} + +int cg_set_group_access( + const char *controller, + const char *path, + mode_t mode, + uid_t uid, + gid_t gid) { + + _cleanup_free_ char *fs = NULL; + int r; + + assert(path); + + if (mode != (mode_t) -1) + mode &= 0777; + + r = cg_get_path(controller, path, NULL, &fs); + if (r < 0) + return r; + + return chmod_and_chown(fs, mode, uid, gid); +} + +int cg_set_task_access( + const char *controller, + const char *path, + mode_t mode, + uid_t uid, + gid_t gid, + int sticky) { + + _cleanup_free_ char *fs = NULL, *procs = NULL; + int r; + + assert(path); + + if (mode == (mode_t) -1 && uid == (uid_t) -1 && gid == (gid_t) -1 && sticky < 0) + return 0; + + if (mode != (mode_t) -1) + mode &= 0666; + + r = cg_get_path(controller, path, "tasks", &fs); + if (r < 0) + return r; + + if (sticky >= 0 && mode != (mode_t) -1) + /* Both mode and sticky param are passed */ + mode |= (sticky ? S_ISVTX : 0); + else if ((sticky >= 0 && mode == (mode_t) -1) || + (mode != (mode_t) -1 && sticky < 0)) { + struct stat st; + + /* Only one param is passed, hence read the current + * mode from the file itself */ + + r = lstat(fs, &st); + if (r < 0) + return -errno; + + if (mode == (mode_t) -1) + /* No mode set, we just shall set the sticky bit */ + mode = (st.st_mode & ~S_ISVTX) | (sticky ? S_ISVTX : 0); + else + /* Only mode set, leave sticky bit untouched */ + mode = (st.st_mode & ~0777) | mode; + } + + r = chmod_and_chown(fs, mode, uid, gid); + if (r < 0) + return r; + + /* Always keep values for "cgroup.procs" in sync with "tasks" */ + r = cg_get_path(controller, path, "cgroup.procs", &procs); + if (r < 0) + return r; + + return chmod_and_chown(procs, mode, uid, gid); +} + +int cg_pid_get_path(const char *controller, pid_t pid, char **path) { + _cleanup_fclose_ FILE *f = NULL; + char line[LINE_MAX]; + const char *fs; + size_t cs; + + assert(path); + assert(pid >= 0); + + if (controller) { + if (!cg_controller_is_valid(controller, true)) + return -EINVAL; + + controller = normalize_controller(controller); + } else + controller = SYSTEMD_CGROUP_CONTROLLER; + + if (pid == 0) + fs = "/proc/self/cgroup"; + else + fs = procfs_file_alloca(pid, "cgroup"); + + f = fopen(fs, "re"); + if (!f) + return errno == ENOENT ? -ESRCH : -errno; + + cs = strlen(controller); + + FOREACH_LINE(line, f, return -errno) { + char *l, *p, *w, *e; + size_t k; + char *state; + bool found = false; + + truncate_nl(line); + + l = strchr(line, ':'); + if (!l) + continue; + + l++; + e = strchr(l, ':'); + if (!e) + continue; + + *e = 0; + + FOREACH_WORD_SEPARATOR(w, k, l, ",", state) { + + if (k == cs && memcmp(w, controller, cs) == 0) { + found = true; + break; + } + + if (k == 5 + cs && + memcmp(w, "name=", 5) == 0 && + memcmp(w+5, controller, cs) == 0) { + found = true; + break; + } + } + + if (!found) + continue; + + p = strdup(e + 1); + if (!p) + return -ENOMEM; + + *path = p; + return 0; + } + + return -ENOENT; +} + +int cg_install_release_agent(const char *controller, const char *agent) { + _cleanup_free_ char *fs = NULL, *contents = NULL; + char *sc; + int r; + + assert(agent); + + r = cg_get_path(controller, NULL, "release_agent", &fs); + if (r < 0) + return r; + + r = read_one_line_file(fs, &contents); + if (r < 0) + return r; + + sc = strstrip(contents); + if (sc[0] == 0) { + r = write_string_file(fs, agent); + if (r < 0) + return r; + } else if (!streq(sc, agent)) + return -EEXIST; + + free(fs); + fs = NULL; + r = cg_get_path(controller, NULL, "notify_on_release", &fs); + if (r < 0) + return r; + + free(contents); + contents = NULL; + r = read_one_line_file(fs, &contents); + if (r < 0) + return r; + + sc = strstrip(contents); + if (streq(sc, "0")) { + r = write_string_file(fs, "1"); + if (r < 0) + return r; + + return 1; + } + + if (!streq(sc, "1")) + return -EIO; + + return 0; +} + +int cg_is_empty(const char *controller, const char *path, bool ignore_self) { + _cleanup_fclose_ FILE *f = NULL; + pid_t pid = 0, self_pid; + bool found = false; + int r; + + assert(path); + + r = cg_enumerate_tasks(controller, path, &f); + if (r < 0) + return r == -ENOENT ? 1 : r; + + self_pid = getpid(); + + while ((r = cg_read_pid(f, &pid)) > 0) { + + if (ignore_self && pid == self_pid) + continue; + + found = true; + break; + } + + if (r < 0) + return r; + + return !found; +} + +int cg_is_empty_by_spec(const char *spec, bool ignore_self) { + _cleanup_free_ char *controller = NULL, *path = NULL; + int r; + + assert(spec); + + r = cg_split_spec(spec, &controller, &path); + if (r < 0) + return r; + + return cg_is_empty(controller, path, ignore_self); +} + +int cg_is_empty_recursive(const char *controller, const char *path, bool ignore_self) { + _cleanup_closedir_ DIR *d = NULL; + char *fn; + int r; + + assert(path); + + r = cg_is_empty(controller, path, ignore_self); + if (r <= 0) + return r; + + r = cg_enumerate_subgroups(controller, path, &d); + if (r < 0) + return r == -ENOENT ? 1 : r; + + while ((r = cg_read_subgroup(d, &fn)) > 0) { + _cleanup_free_ char *p = NULL; + + p = strjoin(path, "/", fn, NULL); + free(fn); + if (!p) + return -ENOMEM; + + r = cg_is_empty_recursive(controller, p, ignore_self); + if (r <= 0) + return r; + } + + if (r < 0) + return r; + + return 1; +} + +int cg_split_spec(const char *spec, char **controller, char **path) { + const char *e; + char *t = NULL, *u = NULL; + _cleanup_free_ char *v = NULL; + + assert(spec); + + if (*spec == '/') { + if (!path_is_safe(spec)) + return -EINVAL; + + if (path) { + t = strdup(spec); + if (!t) + return -ENOMEM; + + path_kill_slashes(t); + *path = t; + } + + if (controller) + *controller = NULL; + + return 0; + } + + e = strchr(spec, ':'); + if (!e) { + if (!cg_controller_is_valid(spec, true)) + return -EINVAL; + + if (controller) { + t = strdup(normalize_controller(spec)); + if (!t) + return -ENOMEM; + + *controller = t; + } + + if (path) + *path = NULL; + + return 0; + } + + v = strndup(spec, e-spec); + if (!v) + return -ENOMEM; + t = strdup(normalize_controller(v)); + if (!t) + return -ENOMEM; + if (!cg_controller_is_valid(t, true)) { + free(t); + return -EINVAL; + } + + u = strdup(e+1); + if (!u) { + free(t); + return -ENOMEM; + } + if (!path_is_safe(u) || + !path_is_absolute(u)) { + free(t); + free(u); + return -EINVAL; + } + + path_kill_slashes(u); + + if (controller) + *controller = t; + else + free(t); + + if (path) + *path = u; + else + free(u); + + return 0; +} + +int cg_join_spec(const char *controller, const char *path, char **spec) { + char *s; + + assert(path); + + if (!controller) + controller = "systemd"; + else { + if (!cg_controller_is_valid(controller, true)) + return -EINVAL; + + controller = normalize_controller(controller); + } + + if (!path_is_absolute(path)) + return -EINVAL; + + s = strjoin(controller, ":", path, NULL); + if (!s) + return -ENOMEM; + + path_kill_slashes(s + strlen(controller) + 1); + + *spec = s; + return 0; +} + +int cg_mangle_path(const char *path, char **result) { + _cleanup_free_ char *c = NULL, *p = NULL; + char *t; + int r; + + assert(path); + assert(result); + + /* First check if it already is a filesystem path */ + if (path_startswith(path, "/sys/fs/cgroup")) { + + t = strdup(path); + if (!t) + return -ENOMEM; + + path_kill_slashes(t); + *result = t; + return 0; + } + + /* Otherwise treat it as cg spec */ + r = cg_split_spec(path, &c, &p); + if (r < 0) + return r; + + return cg_get_path(c ? c : SYSTEMD_CGROUP_CONTROLLER, p ? p : "/", NULL, result); +} + +int cg_get_system_path(char **path) { + char *p; + int r; + + assert(path); + + r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, 1, &p); + if (r < 0) { + p = strdup("/system"); + if (!p) + return -ENOMEM; + } + + if (endswith(p, "/system")) + *path = p; + else { + char *q; + + q = strappend(p, "/system"); + free(p); + if (!q) + return -ENOMEM; + + *path = q; + } + + return 0; +} + +int cg_get_root_path(char **path) { + char *root, *e; + int r; + + assert(path); + + r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, 1, &root); + if (r < 0) + return r; + + e = endswith(root, "/system"); + if (e == root) + e[1] = 0; + else if (e) + *e = 0; + + *path = root; + return 0; +} + +int cg_get_user_path(char **path) { + _cleanup_free_ char *root = NULL; + char *p; + + assert(path); + + /* Figure out the place to put user cgroups below. We use the + * same as PID 1 has but with the "/system" suffix replaced by + * "/user" */ + + if (cg_get_root_path(&root) < 0 || streq(root, "/")) + p = strdup("/user"); + else + p = strappend(root, "/user"); + + if (!p) + return -ENOMEM; + + *path = p; + return 0; +} + +int cg_get_machine_path(const char *machine, char **path) { + _cleanup_free_ char *root = NULL, *escaped = NULL; + char *p; + + assert(path); + + if (machine) { + const char *name = strappenda(machine, ".nspawn"); + + escaped = cg_escape(name); + if (!escaped) + return -ENOMEM; + } + + p = strjoin(cg_get_root_path(&root) >= 0 && !streq(root, "/") ? root : "", + "/machine", machine ? "/" : "", machine ? escaped : "", NULL); + if (!p) + return -ENOMEM; + + *path = p; + return 0; +} + +char **cg_shorten_controllers(char **controllers) { + char **f, **t; + + if (!controllers) + return controllers; + + for (f = controllers, t = controllers; *f; f++) { + const char *p; + int r; + + p = normalize_controller(*f); + + if (streq(p, "systemd")) { + free(*f); + continue; + } + + if (!cg_controller_is_valid(p, true)) { + log_warning("Controller %s is not valid, removing from controllers list.", p); + free(*f); + continue; + } + + r = check_hierarchy(p); + if (r < 0) { + log_debug("Controller %s is not available, removing from controllers list.", p); + free(*f); + continue; + } + + *(t++) = *f; + } + + *t = NULL; + return strv_uniq(controllers); +} + +int cg_pid_get_path_shifted(pid_t pid, char **root, char **cgroup) { + _cleanup_free_ char *cg_root = NULL; + char *cg_process, *p; + int r; + + r = cg_get_root_path(&cg_root); + if (r < 0) + return r; + + r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, pid, &cg_process); + if (r < 0) + return r; + + p = path_startswith(cg_process, cg_root); + if (p) + p--; + else + p = cg_process; + + if (cgroup) { + char* c; + + c = strdup(p); + if (!c) { + free(cg_process); + return -ENOMEM; + } + + *cgroup = c; + } + + if (root) { + cg_process[p-cg_process] = 0; + *root = cg_process; + } else + free(cg_process); + + return 0; +} + +int cg_path_decode_unit(const char *cgroup, char **unit){ + char *p, *e, *c, *s, *k; + + assert(cgroup); + assert(unit); + + e = strchrnul(cgroup, '/'); + c = strndupa(cgroup, e - cgroup); + c = cg_unescape(c); + + /* Could this be a valid unit name? */ + if (!unit_name_is_valid(c, true)) + return -EINVAL; + + if (!unit_name_is_template(c)) + s = strdup(c); + else { + if (*e != '/') + return -EINVAL; + + e += strspn(e, "/"); + + p = strchrnul(e, '/'); + k = strndupa(e, p - e); + k = cg_unescape(k); + + if (!unit_name_is_valid(k, false)) + return -EINVAL; + + s = strdup(k); + } + + if (!s) + return -ENOMEM; + + *unit = s; + return 0; +} + +int cg_path_get_unit(const char *path, char **unit) { + const char *e; + + assert(path); + assert(unit); + + e = path_startswith(path, "/system/"); + if (!e) + return -ENOENT; + + return cg_path_decode_unit(e, unit); +} + +int cg_pid_get_unit(pid_t pid, char **unit) { + _cleanup_free_ char *cgroup = NULL; + int r; + + assert(unit); + + r = cg_pid_get_path_shifted(pid, NULL, &cgroup); + if (r < 0) + return r; + + return cg_path_get_unit(cgroup, unit); +} + +_pure_ static const char *skip_label(const char *e) { + assert(e); + + e = strchr(e, '/'); + if (!e) + return NULL; + + e += strspn(e, "/"); + return e; +} + +int cg_path_get_user_unit(const char *path, char **unit) { + const char *e; + + assert(path); + assert(unit); + + /* We always have to parse the path from the beginning as unit + * cgroups might have arbitrary child cgroups and we shouldn't get + * confused by those */ + + e = path_startswith(path, "/user/"); + if (!e) + return -ENOENT; + + /* Skip the user name */ + e = skip_label(e); + if (!e) + return -ENOENT; + + /* Skip the session ID */ + e = skip_label(e); + if (!e) + return -ENOENT; + + /* Skip the systemd cgroup */ + e = skip_label(e); + if (!e) + return -ENOENT; + + return cg_path_decode_unit(e, unit); +} + +int cg_pid_get_user_unit(pid_t pid, char **unit) { + _cleanup_free_ char *cgroup = NULL; + int r; + + assert(unit); + + r = cg_pid_get_path_shifted(pid, NULL, &cgroup); + if (r < 0) + return r; + + return cg_path_get_user_unit(cgroup, unit); +} + +int cg_path_get_machine_name(const char *path, char **machine) { + const char *e, *n; + char *s, *r; + + assert(path); + assert(machine); + + e = path_startswith(path, "/machine/"); + if (!e) + return -ENOENT; + + n = strchrnul(e, '/'); + if (e == n) + return -ENOENT; + + s = strndupa(e, n - e); + + r = strdup(cg_unescape(s)); + if (!r) + return -ENOMEM; + + *machine = r; + return 0; +} + +int cg_pid_get_machine_name(pid_t pid, char **machine) { + _cleanup_free_ char *cgroup = NULL; + int r; + + assert(machine); + + r = cg_pid_get_path_shifted(pid, NULL, &cgroup); + if (r < 0) + return r; + + return cg_path_get_machine_name(cgroup, machine); +} + +int cg_path_get_session(const char *path, char **session) { + const char *e, *n; + char *s; + + assert(path); + assert(session); + + e = path_startswith(path, "/user/"); + if (!e) + return -ENOENT; + + /* Skip the user name */ + e = skip_label(e); + if (!e) + return -ENOENT; + + n = strchrnul(e, '/'); + if (n - e < 8) + return -ENOENT; + if (memcmp(n - 8, ".session", 8) != 0) + return -ENOENT; + + s = strndup(e, n - e - 8); + if (!s) + return -ENOMEM; + + *session = s; + return 0; +} + +int cg_pid_get_session(pid_t pid, char **session) { + _cleanup_free_ char *cgroup = NULL; + int r; + + assert(session); + + r = cg_pid_get_path_shifted(pid, NULL, &cgroup); + if (r < 0) + return r; + + return cg_path_get_session(cgroup, session); +} + +int cg_path_get_owner_uid(const char *path, uid_t *uid) { + const char *e, *n; + char *s; + + assert(path); + assert(uid); + + e = path_startswith(path, "/user/"); + if (!e) + return -ENOENT; + + n = strchrnul(e, '/'); + if (n - e < 5) + return -ENOENT; + if (memcmp(n - 5, ".user", 5) != 0) + return -ENOENT; + + s = strndupa(e, n - e - 5); + if (!s) + return -ENOMEM; + + return parse_uid(s, uid); +} + +int cg_pid_get_owner_uid(pid_t pid, uid_t *uid) { + _cleanup_free_ char *cgroup = NULL; + int r; + + assert(uid); + + r = cg_pid_get_path_shifted(pid, NULL, &cgroup); + if (r < 0) + return r; + + return cg_path_get_owner_uid(cgroup, uid); +} + +int cg_controller_from_attr(const char *attr, char **controller) { + const char *dot; + char *c; + + assert(attr); + assert(controller); + + if (!filename_is_safe(attr)) + return -EINVAL; + + dot = strchr(attr, '.'); + if (!dot) { + *controller = NULL; + return 0; + } + + c = strndup(attr, dot - attr); + if (!c) + return -ENOMEM; + + if (!cg_controller_is_valid(c, false)) { + free(c); + return -EINVAL; + } + + *controller = c; + return 1; +} + +char *cg_escape(const char *p) { + bool need_prefix = false; + + /* This implements very minimal escaping for names to be used + * as file names in the cgroup tree: any name which might + * conflict with a kernel name or is prefixed with '_' is + * prefixed with a '_'. That way, when reading cgroup names it + * is sufficient to remove a single prefixing underscore if + * there is one. */ + + /* The return value of this function (unlike cg_unescape()) + * needs free()! */ + + if (p[0] == 0 || + p[0] == '_' || + p[0] == '.' || + streq(p, "notify_on_release") || + streq(p, "release_agent") || + streq(p, "tasks")) + need_prefix = true; + else { + const char *dot; + + dot = strrchr(p, '.'); + if (dot) { + + if (dot - p == 6 && memcmp(p, "cgroup", 6) == 0) + need_prefix = true; + else { + char *n; + + n = strndupa(p, dot - p); + + if (check_hierarchy(n) >= 0) + need_prefix = true; + } + } + } + + if (need_prefix) + return strappend("_", p); + else + return strdup(p); +} + +char *cg_unescape(const char *p) { + assert(p); + + /* The return value of this function (unlike cg_escape()) + * doesn't need free()! */ + + if (p[0] == '_') + return (char*) p+1; + + return (char*) p; +} + +#define CONTROLLER_VALID \ + "0123456789" \ + "abcdefghijklmnopqrstuvwxyz" \ + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \ + "_" + +bool cg_controller_is_valid(const char *p, bool allow_named) { + const char *t, *s; + + if (!p) + return false; + + if (allow_named) { + s = startswith(p, "name="); + if (s) + p = s; + } + + if (*p == 0 || *p == '_') + return false; + + for (t = p; *t; t++) + if (!strchr(CONTROLLER_VALID, *t)) + return false; + + if (t - p > FILENAME_MAX) + return false; + + return true; +} diff --git a/src/shared/cgroup-util.h b/src/shared/cgroup-util.h new file mode 100644 index 0000000000..25dd277ba5 --- /dev/null +++ b/src/shared/cgroup-util.h @@ -0,0 +1,115 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <sys/types.h> +#include <stdio.h> +#include <dirent.h> + +#include "set.h" +#include "def.h" + +/* + * General rules: + * + * We accept named hierarchies in the syntax "foo" and "name=foo". + * + * We expect that named hierarchies do not conflict in name with a + * kernel hierarchy, modulo the "name=" prefix. + * + * We always generate "normalized" controller names, i.e. without the + * "name=" prefix. + * + * We require absolute cgroup paths. When returning, we will always + * generate paths with multiple adjacent / removed. + */ + +int cg_enumerate_processes(const char *controller, const char *path, FILE **_f); +int cg_enumerate_tasks(const char *controller, const char *path, FILE **_f); +int cg_read_pid(FILE *f, pid_t *_pid); + +int cg_enumerate_subgroups(const char *controller, const char *path, DIR **_d); +int cg_read_subgroup(DIR *d, char **fn); + +int cg_kill(const char *controller, const char *path, int sig, bool sigcont, bool ignore_self, Set *s); +int cg_kill_recursive(const char *controller, const char *path, int sig, bool sigcont, bool ignore_self, bool remove, Set *s); +int cg_kill_recursive_and_wait(const char *controller, const char *path, bool remove); + +int cg_migrate(const char *cfrom, const char *pfrom, const char *cto, const char *pto, bool ignore_self); +int cg_migrate_recursive(const char *cfrom, const char *pfrom, const char *cto, const char *pto, bool ignore_self, bool remove); + +int cg_split_spec(const char *spec, char **controller, char **path); +int cg_join_spec(const char *controller, const char *path, char **spec); +int cg_mangle_path(const char *path, char **result); + +int cg_get_path(const char *controller, const char *path, const char *suffix, char **fs); +int cg_get_path_and_check(const char *controller, const char *path, const char *suffix, char **fs); + +int cg_pid_get_path(const char *controller, pid_t pid, char **path); + +int cg_trim(const char *controller, const char *path, bool delete_root); + +int cg_rmdir(const char *controller, const char *path, bool honour_sticky); +int cg_delete(const char *controller, const char *path); + +int cg_create(const char *controller, const char *path, const char *suffix); +int cg_attach(const char *controller, const char *path, pid_t pid); +int cg_create_and_attach(const char *controller, const char *path, pid_t pid); + +int cg_set_group_access(const char *controller, const char *path, mode_t mode, uid_t uid, gid_t gid); +int cg_set_task_access(const char *controller, const char *path, mode_t mode, uid_t uid, gid_t gid, int sticky); + +int cg_install_release_agent(const char *controller, const char *agent); + +int cg_is_empty(const char *controller, const char *path, bool ignore_self); +int cg_is_empty_by_spec(const char *spec, bool ignore_self); +int cg_is_empty_recursive(const char *controller, const char *path, bool ignore_self); + +int cg_get_root_path(char **path); +int cg_get_system_path(char **path); +int cg_get_user_path(char **path); +int cg_get_machine_path(const char *machine, char **path); + +int cg_path_get_session(const char *path, char **session); +int cg_path_get_owner_uid(const char *path, uid_t *uid); +int cg_path_get_unit(const char *path, char **unit); +int cg_path_get_user_unit(const char *path, char **unit); +int cg_path_get_machine_name(const char *path, char **machine); + +int cg_pid_get_path_shifted(pid_t pid, char **root, char **cgroup); + +int cg_pid_get_session(pid_t pid, char **session); +int cg_pid_get_owner_uid(pid_t pid, uid_t *uid); +int cg_pid_get_unit(pid_t pid, char **unit); +int cg_pid_get_user_unit(pid_t pid, char **unit); +int cg_pid_get_machine_name(pid_t pid, char **machine); + +int cg_path_decode_unit(const char *cgroup, char **unit); + +char **cg_shorten_controllers(char **controllers); + +int cg_controller_from_attr(const char *attr, char **controller); + +char *cg_escape(const char *p); +char *cg_unescape(const char *p) _pure_; + +bool cg_controller_is_valid(const char *p, bool allow_named); diff --git a/src/shared/conf-files.c b/src/shared/conf-files.c new file mode 100644 index 0000000000..6d99739353 --- /dev/null +++ b/src/shared/conf-files.c @@ -0,0 +1,178 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <assert.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <stdlib.h> +#include <stdio.h> +#include <sys/stat.h> +#include <dirent.h> + +#include "macro.h" +#include "util.h" +#include "missing.h" +#include "log.h" +#include "strv.h" +#include "path-util.h" +#include "hashmap.h" +#include "conf-files.h" + +static int files_add(Hashmap *h, const char *root, const char *path, const char *suffix) { + _cleanup_closedir_ DIR *dir = NULL; + _cleanup_free_ char *dirpath = NULL; + + if (asprintf(&dirpath, "%s%s", root ? root : "", path) < 0) + return -ENOMEM; + + dir = opendir(dirpath); + if (!dir) { + if (errno == ENOENT) + return 0; + return -errno; + } + + for (;;) { + struct dirent *de; + union dirent_storage buf; + char *p; + int r; + + r = readdir_r(dir, &buf.de, &de); + if (r != 0) + return -r; + + if (!de) + break; + + if (!dirent_is_file_with_suffix(de, suffix)) + continue; + + p = strjoin(dirpath, "/", de->d_name, NULL); + if (!p) + return -ENOMEM; + + r = hashmap_put(h, path_get_file_name(p), p); + if (r == -EEXIST) { + log_debug("Skipping overridden file: %s.", p); + free(p); + } else if (r < 0) { + free(p); + return r; + } else if (r == 0) { + log_debug("Duplicate file %s", p); + free(p); + } + } + + return 0; +} + +static int base_cmp(const void *a, const void *b) { + const char *s1, *s2; + + s1 = *(char * const *)a; + s2 = *(char * const *)b; + return strcmp(path_get_file_name(s1), path_get_file_name(s2)); +} + +static int conf_files_list_strv_internal(char ***strv, const char *suffix, const char *root, char **dirs) { + Hashmap *fh; + char **files, **p; + int r; + + assert(strv); + assert(suffix); + + /* This alters the dirs string array */ + if (!path_strv_canonicalize_uniq(dirs)) + return -ENOMEM; + + fh = hashmap_new(string_hash_func, string_compare_func); + if (!fh) + return -ENOMEM; + + STRV_FOREACH(p, dirs) { + r = files_add(fh, root, *p, suffix); + if (r == -ENOMEM) { + hashmap_free_free(fh); + return r; + } else if (r < 0) + log_debug("Failed to search for files in %s: %s", + *p, strerror(-r)); + } + + files = hashmap_get_strv(fh); + if (files == NULL) { + hashmap_free_free(fh); + return -ENOMEM; + } + + qsort(files, hashmap_size(fh), sizeof(char *), base_cmp); + *strv = files; + + hashmap_free(fh); + return 0; +} + +int conf_files_list_strv(char ***strv, const char *suffix, const char *root, const char* const* dirs) { + _cleanup_strv_free_ char **copy = NULL; + + assert(strv); + assert(suffix); + + copy = strv_copy((char**) dirs); + if (!copy) + return -ENOMEM; + + return conf_files_list_strv_internal(strv, suffix, root, copy); +} + +int conf_files_list(char ***strv, const char *suffix, const char *root, const char *dir, ...) { + _cleanup_strv_free_ char **dirs = NULL; + va_list ap; + + assert(strv); + assert(suffix); + + va_start(ap, dir); + dirs = strv_new_ap(dir, ap); + va_end(ap); + + if (!dirs) + return -ENOMEM; + + return conf_files_list_strv_internal(strv, suffix, root, dirs); +} + +int conf_files_list_nulstr(char ***strv, const char *suffix, const char *root, const char *d) { + _cleanup_strv_free_ char **dirs = NULL; + + assert(strv); + assert(suffix); + + dirs = strv_split_nulstr(d); + if (!dirs) + return -ENOMEM; + + return conf_files_list_strv_internal(strv, suffix, root, dirs); +} diff --git a/src/shared/conf-files.h b/src/shared/conf-files.h new file mode 100644 index 0000000000..3bd3d2f3d4 --- /dev/null +++ b/src/shared/conf-files.h @@ -0,0 +1,32 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef fooconffileshfoo +#define fooconffileshfoo + +/*** + This file is part of systemd. + + Copyright 2010-2012 Lennart Poettering + Copyright 2010-2012 Kay Sievers + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "macro.h" + +int conf_files_list(char ***strv, const char *suffix, const char *root, const char *dir, ...); +int conf_files_list_strv(char ***strv, const char *suffix, const char *root, const char* const* dirs); +int conf_files_list_nulstr(char ***strv, const char *suffix, const char *root, const char *dirs); + +#endif diff --git a/src/shared/conf-parser.c b/src/shared/conf-parser.c new file mode 100644 index 0000000000..2303d9a50b --- /dev/null +++ b/src/shared/conf-parser.c @@ -0,0 +1,920 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <string.h> +#include <stdio.h> +#include <errno.h> +#include <assert.h> +#include <stdlib.h> + +#include "conf-parser.h" +#include "util.h" +#include "macro.h" +#include "strv.h" +#include "log.h" +#include "utf8.h" +#include "path-util.h" +#include "set.h" +#include "exit-status.h" +#include "sd-messages.h" + +int log_syntax_internal(const char *unit, int level, + const char *file, unsigned line, const char *func, + const char *config_file, unsigned config_line, + int error, const char *format, ...) { + + _cleanup_free_ char *msg = NULL; + int r; + va_list ap; + + va_start(ap, format); + r = vasprintf(&msg, format, ap); + va_end(ap); + if (r < 0) + return log_oom(); + + if (unit) + r = log_struct_internal(level, + file, line, func, + getpid() == 1 ? "UNIT=%s" : "USER_UNIT=%s", unit, + MESSAGE_ID(SD_MESSAGE_CONFIG_ERROR), + "CONFIG_FILE=%s", config_file, + "CONFIG_LINE=%u", config_line, + "ERRNO=%d", error > 0 ? error : EINVAL, + "MESSAGE=[%s:%u] %s", config_file, config_line, msg, + NULL); + else + r = log_struct_internal(level, + file, line, func, + MESSAGE_ID(SD_MESSAGE_CONFIG_ERROR), + "CONFIG_FILE=%s", config_file, + "CONFIG_LINE=%u", config_line, + "ERRNO=%d", error > 0 ? error : EINVAL, + "MESSAGE=[%s:%u] %s", config_file, config_line, msg, + NULL); + + return r; +} + +int config_item_table_lookup( + void *table, + const char *section, + const char *lvalue, + ConfigParserCallback *func, + int *ltype, + void **data, + void *userdata) { + + ConfigTableItem *t; + + assert(table); + assert(lvalue); + assert(func); + assert(ltype); + assert(data); + + for (t = table; t->lvalue; t++) { + + if (!streq(lvalue, t->lvalue)) + continue; + + if (!streq_ptr(section, t->section)) + continue; + + *func = t->parse; + *ltype = t->ltype; + *data = t->data; + return 1; + } + + return 0; +} + +int config_item_perf_lookup( + void *table, + const char *section, + const char *lvalue, + ConfigParserCallback *func, + int *ltype, + void **data, + void *userdata) { + + ConfigPerfItemLookup lookup = (ConfigPerfItemLookup) table; + const ConfigPerfItem *p; + + assert(table); + assert(lvalue); + assert(func); + assert(ltype); + assert(data); + + if (!section) + p = lookup(lvalue, strlen(lvalue)); + else { + char *key; + + key = strjoin(section, ".", lvalue, NULL); + if (!key) + return -ENOMEM; + + p = lookup(key, strlen(key)); + free(key); + } + + if (!p) + return 0; + + *func = p->parse; + *ltype = p->ltype; + *data = (uint8_t*) userdata + p->offset; + return 1; +} + +/* Run the user supplied parser for an assignment */ +static int next_assignment(const char *unit, + const char *filename, + unsigned line, + ConfigItemLookup lookup, + void *table, + const char *section, + const char *lvalue, + const char *rvalue, + bool relaxed, + void *userdata) { + + ConfigParserCallback func = NULL; + int ltype = 0; + void *data = NULL; + int r; + + assert(filename); + assert(line > 0); + assert(lookup); + assert(lvalue); + assert(rvalue); + + r = lookup(table, section, lvalue, &func, <ype, &data, userdata); + if (r < 0) + return r; + + if (r > 0) { + if (func) + return func(unit, filename, line, section, lvalue, ltype, + rvalue, data, userdata); + + return 0; + } + + /* Warn about unknown non-extension fields. */ + if (!relaxed && !startswith(lvalue, "X-")) + log_syntax(unit, LOG_WARNING, filename, line, EINVAL, + "Unknown lvalue '%s' in section '%s'", lvalue, section); + + return 0; +} + +/* Parse a variable assignment line */ +static int parse_line(const char* unit, + const char *filename, + unsigned line, + const char *sections, + ConfigItemLookup lookup, + void *table, + bool relaxed, + bool allow_include, + char **section, + char *l, + void *userdata) { + + char *e; + + assert(filename); + assert(line > 0); + assert(lookup); + assert(l); + + l = strstrip(l); + + if (!*l) + return 0; + + if (strchr(COMMENTS "\n", *l)) + return 0; + + if (startswith(l, ".include ")) { + _cleanup_free_ char *fn = NULL; + + if (!allow_include) { + log_syntax(unit, LOG_ERR, filename, line, EBADMSG, + ".include not allowed here. Ignoring."); + return 0; + } + + fn = file_in_same_dir(filename, strstrip(l+9)); + if (!fn) + return -ENOMEM; + + return config_parse(unit, fn, NULL, sections, lookup, table, relaxed, false, userdata); + } + + if (*l == '[') { + size_t k; + char *n; + + k = strlen(l); + assert(k > 0); + + if (l[k-1] != ']') { + log_syntax(unit, LOG_ERR, filename, line, EBADMSG, + "Invalid section header '%s'", l); + return -EBADMSG; + } + + n = strndup(l+1, k-2); + if (!n) + return -ENOMEM; + + if (sections && !nulstr_contains(sections, n)) { + + if (!relaxed) + log_syntax(unit, LOG_WARNING, filename, line, EINVAL, + "Unknown section '%s'. Ignoring.", n); + + free(n); + *section = NULL; + } else { + free(*section); + *section = n; + } + + return 0; + } + + if (sections && !*section) { + + if (!relaxed) + log_syntax(unit, LOG_WARNING, filename, line, EINVAL, + "Assignment outside of section. Ignoring."); + + return 0; + } + + e = strchr(l, '='); + if (!e) { + log_syntax(unit, LOG_WARNING, filename, line, EINVAL, "Missing '='."); + return -EBADMSG; + } + + *e = 0; + e++; + + return next_assignment(unit, + filename, + line, + lookup, + table, + *section, + strstrip(l), + strstrip(e), + relaxed, + userdata); +} + +/* Go through the file and parse each line */ +int config_parse(const char *unit, + const char *filename, + FILE *f, + const char *sections, + ConfigItemLookup lookup, + void *table, + bool relaxed, + bool allow_include, + void *userdata) { + + _cleanup_free_ char *section = NULL, *continuation = NULL; + _cleanup_fclose_ FILE *ours = NULL; + unsigned line = 0; + int r; + + assert(filename); + assert(lookup); + + if (!f) { + f = ours = fopen(filename, "re"); + if (!f) { + log_error("Failed to open configuration file '%s': %m", filename); + return -errno; + } + } + + while (!feof(f)) { + char l[LINE_MAX], *p, *c = NULL, *e; + bool escaped = false; + + if (!fgets(l, sizeof(l), f)) { + if (feof(f)) + break; + + log_error("Failed to read configuration file '%s': %m", filename); + return -errno; + } + + truncate_nl(l); + + if (continuation) { + c = strappend(continuation, l); + if (!c) + return -ENOMEM; + + free(continuation); + continuation = NULL; + p = c; + } else + p = l; + + for (e = p; *e; e++) { + if (escaped) + escaped = false; + else if (*e == '\\') + escaped = true; + } + + if (escaped) { + *(e-1) = ' '; + + if (c) + continuation = c; + else { + continuation = strdup(l); + if (!continuation) + return -ENOMEM; + } + + continue; + } + + r = parse_line(unit, + filename, + ++line, + sections, + lookup, + table, + relaxed, + allow_include, + §ion, + p, + userdata); + free(c); + + if (r < 0) + return r; + } + + return 0; +} + +#define DEFINE_PARSER(type, vartype, conv_func) \ + int config_parse_##type(const char *unit, \ + const char *filename, \ + unsigned line, \ + const char *section, \ + const char *lvalue, \ + int ltype, \ + const char *rvalue, \ + void *data, \ + void *userdata) { \ + \ + vartype *i = data; \ + int r; \ + \ + assert(filename); \ + assert(lvalue); \ + assert(rvalue); \ + assert(data); \ + \ + r = conv_func(rvalue, i); \ + if (r < 0) \ + log_syntax(unit, LOG_ERR, filename, line, -r, \ + "Failed to parse %s value, ignoring: %s", \ + #vartype, rvalue); \ + \ + return 0; \ + } + +DEFINE_PARSER(int, int, safe_atoi) +DEFINE_PARSER(long, long, safe_atoli) +DEFINE_PARSER(uint64, uint64_t, safe_atou64) +DEFINE_PARSER(unsigned, unsigned, safe_atou) +DEFINE_PARSER(double, double, safe_atod) +DEFINE_PARSER(nsec, nsec_t, parse_nsec) +DEFINE_PARSER(sec, usec_t, parse_sec) + + +int config_parse_bytes_size(const char* unit, + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + size_t *sz = data; + off_t o; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = parse_bytes(rvalue, &o); + if (r < 0 || (off_t) (size_t) o != o) { + log_syntax(unit, LOG_ERR, filename, line, -r, + "Failed to parse byte value, ignoring: %s", rvalue); + return 0; + } + + *sz = (size_t) o; + return 0; +} + + +int config_parse_bytes_off(const char* unit, + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + off_t *bytes = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + assert_cc(sizeof(off_t) == sizeof(uint64_t)); + + r = parse_bytes(rvalue, bytes); + if (r < 0) + log_syntax(unit, LOG_ERR, filename, line, -r, + "Failed to parse bytes value, ignoring: %s", rvalue); + + return 0; +} + +int config_parse_bool(const char* unit, + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + int k; + bool *b = data; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + k = parse_boolean(rvalue); + if (k < 0) { + log_syntax(unit, LOG_ERR, filename, line, -k, + "Failed to parse boolean value, ignoring: %s", rvalue); + return 0; + } + + *b = !!k; + return 0; +} + +int config_parse_tristate(const char *unit, + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + int k; + int *b = data; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + /* Tristates are like booleans, but can also take the 'default' value, i.e. "-1" */ + + k = parse_boolean(rvalue); + if (k < 0) { + log_syntax(unit, LOG_ERR, filename, line, -k, + "Failed to parse boolean value, ignoring: %s", rvalue); + return 0; + } + + *b = !!k; + return 0; +} + +int config_parse_string(const char *unit, + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + char **s = data; + char *n; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + n = strdup(rvalue); + if (!n) + return log_oom(); + + if (!utf8_is_valid(n)) { + log_syntax(unit, LOG_ERR, filename, line, EINVAL, + "String is not UTF-8 clean, ignoring assignment: %s", rvalue); + free(n); + return 0; + } + + free(*s); + if (*n) + *s = n; + else { + free(n); + *s = NULL; + } + + return 0; +} + +int config_parse_path(const char *unit, + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + char **s = data; + char *n; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (!utf8_is_valid(rvalue)) { + log_syntax(unit, LOG_ERR, filename, line, EINVAL, + "Path is not UTF-8 clean, ignoring assignment: %s", rvalue); + return 0; + } + + if (!path_is_absolute(rvalue)) { + log_syntax(unit, LOG_ERR, filename, line, EINVAL, + "Not an absolute path, ignoring: %s", rvalue); + return 0; + } + + n = strdup(rvalue); + if (!n) + return log_oom(); + + path_kill_slashes(n); + + free(*s); + *s = n; + + return 0; +} + +int config_parse_strv(const char *unit, + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + char *** sv = data, *w, *state; + size_t l; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (isempty(rvalue)) { + char **empty; + + /* Empty assignment resets the list. As a special rule + * we actually fill in a real empty array here rather + * than NULL, since some code wants to know if + * something was set at all... */ + empty = strv_new(NULL, NULL); + if (!empty) + return log_oom(); + + strv_free(*sv); + *sv = empty; + return 0; + } + + FOREACH_WORD_QUOTED(w, l, rvalue, state) { + _cleanup_free_ char *n; + + n = cunescape_length(w, l); + if (!n) + return log_oom(); + + if (!utf8_is_valid(n)) { + log_syntax(unit, LOG_ERR, filename, line, EINVAL, + "String is not UTF-8 clean, ignoring: %s", rvalue); + continue; + } + + r = strv_extend(sv, n); + if (r < 0) + return log_oom(); + } + + return 0; +} + +int config_parse_path_strv(const char *unit, + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + char*** sv = data, *w, *state; + size_t l; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (isempty(rvalue)) { + /* Empty assignment resets the list */ + strv_free(*sv); + *sv = NULL; + return 0; + } + + FOREACH_WORD_QUOTED(w, l, rvalue, state) { + _cleanup_free_ char *n; + + n = strndup(w, l); + if (!n) + return log_oom(); + + if (!utf8_is_valid(n)) { + log_syntax(unit, LOG_ERR, filename, line, EINVAL, + "Path is not UTF-8 clean, ignoring assignment: %s", rvalue); + continue; + } + + if (!path_is_absolute(n)) { + log_syntax(unit, LOG_ERR, filename, line, EINVAL, + "Not an absolute path, ignoring: %s", rvalue); + continue; + } + + path_kill_slashes(n); + r = strv_extend(sv, n); + if (r < 0) + return log_oom(); + } + + return 0; +} + +int config_parse_mode(const char *unit, + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + mode_t *m = data; + long l; + char *x = NULL; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + errno = 0; + l = strtol(rvalue, &x, 8); + if (!x || x == rvalue || *x || errno) { + log_syntax(unit, LOG_ERR, filename, line, errno, + "Failed to parse mode value, ignoring: %s", rvalue); + return 0; + } + + if (l < 0000 || l > 07777) { + log_syntax(unit, LOG_ERR, filename, line, ERANGE, + "Mode value out of range, ignoring: %s", rvalue); + return 0; + } + + *m = (mode_t) l; + return 0; +} + +int config_parse_facility(const char *unit, + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + + int *o = data, x; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + x = log_facility_unshifted_from_string(rvalue); + if (x < 0) { + log_syntax(unit, LOG_ERR, filename, line, EINVAL, + "Failed to parse log facility, ignoring: %s", rvalue); + return 0; + } + + *o = (x << 3) | LOG_PRI(*o); + + return 0; +} + +int config_parse_level(const char *unit, + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + + int *o = data, x; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + x = log_level_from_string(rvalue); + if (x < 0) { + log_syntax(unit, LOG_ERR, filename, line, EINVAL, + "Failed to parse log level, ignoring: %s", rvalue); + return 0; + } + + *o = (*o & LOG_FACMASK) | x; + return 0; +} + +int config_parse_set_status(const char *unit, + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + char *w; + size_t l; + char *state; + int r; + ExitStatusSet *status_set = data; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (isempty(rvalue)) { + /* Empty assignment resets the list */ + + set_free(status_set->signal); + set_free(status_set->code); + + status_set->signal = status_set->code = NULL; + return 0; + } + + FOREACH_WORD(w, l, rvalue, state) { + int val; + char *temp; + + temp = strndup(w, l); + if (!temp) + return log_oom(); + + r = safe_atoi(temp, &val); + if (r < 0) { + val = signal_from_string_try_harder(temp); + free(temp); + + if (val > 0) { + r = set_ensure_allocated(&status_set->signal, trivial_hash_func, trivial_compare_func); + if (r < 0) + return log_oom(); + + r = set_put(status_set->signal, INT_TO_PTR(val)); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, -r, + "Unable to store: %s", w); + return r; + } + } else { + log_syntax(unit, LOG_ERR, filename, line, -val, + "Failed to parse value, ignoring: %s", w); + return 0; + } + } else { + free(temp); + + if (val < 0 || val > 255) + log_syntax(unit, LOG_ERR, filename, line, ERANGE, + "Value %d is outside range 0-255, ignoring", val); + else { + r = set_ensure_allocated(&status_set->code, trivial_hash_func, trivial_compare_func); + if (r < 0) + return log_oom(); + + r = set_put(status_set->code, INT_TO_PTR(val)); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, -r, + "Unable to store: %s", w); + return r; + } + } + } + } + + return 0; +} diff --git a/src/shared/conf-parser.h b/src/shared/conf-parser.h new file mode 100644 index 0000000000..08428a514a --- /dev/null +++ b/src/shared/conf-parser.h @@ -0,0 +1,151 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <stdio.h> +#include <stdbool.h> + +#include "macro.h" + +/* An abstract parser for simple, line based, shallow configuration + * files consisting of variable assignments only. */ + +/* Prototype for a parser for a specific configuration setting */ +typedef int (*ConfigParserCallback)(const char *unit, + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata); + +/* Wraps information for parsing a specific configuration variable, to + * be stored in a simple array */ +typedef struct ConfigTableItem { + const char *section; /* Section */ + const char *lvalue; /* Name of the variable */ + ConfigParserCallback parse; /* Function that is called to parse the variable's value */ + int ltype; /* Distinguish different variables passed to the same callback */ + void *data; /* Where to store the variable's data */ +} ConfigTableItem; + +/* Wraps information for parsing a specific configuration variable, to + * ve srored in a gperf perfect hashtable */ +typedef struct ConfigPerfItem { + const char *section_and_lvalue; /* Section + "." + name of the variable */ + ConfigParserCallback parse; /* Function that is called to parse the variable's value */ + int ltype; /* Distinguish different variables passed to the same callback */ + size_t offset; /* Offset where to store data, from the beginning of userdata */ +} ConfigPerfItem; + +/* Prototype for a low-level gperf lookup function */ +typedef const ConfigPerfItem* (*ConfigPerfItemLookup)(const char *section_and_lvalue, unsigned length); + +/* Prototype for a generic high-level lookup function */ +typedef int (*ConfigItemLookup)( + void *table, + const char *section, + const char *lvalue, + ConfigParserCallback *func, + int *ltype, + void **data, + void *userdata); + +/* Linear table search implementation of ConfigItemLookup, based on + * ConfigTableItem arrays */ +int config_item_table_lookup(void *table, const char *section, const char *lvalue, ConfigParserCallback *func, int *ltype, void **data, void *userdata); + +/* gperf implementation of ConfigItemLookup, based on gperf + * ConfigPerfItem tables */ +int config_item_perf_lookup(void *table, const char *section, const char *lvalue, ConfigParserCallback *func, int *ltype, void **data, void *userdata); + +int config_parse(const char *unit, + const char *filename, + FILE *f, + const char *sections, /* nulstr */ + ConfigItemLookup lookup, + void *table, + bool relaxed, + bool allow_include, + void *userdata); + +/* Generic parsers */ +int config_parse_int(const char *unit, const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_unsigned(const char *unit, const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_long(const char *unit, const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_uint64(const char *unit, const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_double(const char *unit, const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_bytes_size(const char *unit, const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_bytes_off(const char *unit, const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_bool(const char *unit, const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_tristate(const char *unit, const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_string(const char *unit, const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_path(const char *unit, const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_strv(const char *unit, const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_path_strv(const char *unit, const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_sec(const char *unit, const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_nsec(const char *unit, const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_mode(const char *unit, const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_facility(const char *unit, const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_level(const char *unit, const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_set_status(const char *unit, const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); + +int log_syntax_internal(const char *unit, int level, + const char *file, unsigned line, const char *func, + const char *config_file, unsigned config_line, + int error, const char *format, ...) _printf_attr_(9, 10); + +#define log_syntax(unit, level, config_file, config_line, error, ...) \ + log_syntax_internal(unit, level, \ + __FILE__, __LINE__, __func__, \ + config_file, config_line, \ + error, __VA_ARGS__) + +#define DEFINE_CONFIG_PARSE_ENUM(function,name,type,msg) \ + int function(const char *unit, \ + const char *filename, \ + unsigned line, \ + const char *section, \ + const char *lvalue, \ + int ltype, \ + const char *rvalue, \ + void *data, \ + void *userdata) { \ + \ + type *i = data, x; \ + \ + assert(filename); \ + assert(lvalue); \ + assert(rvalue); \ + assert(data); \ + \ + if ((x = name##_from_string(rvalue)) < 0) { \ + log_syntax(unit, LOG_ERR, filename, line, -x, \ + msg ", ignoring: %s", rvalue); \ + return 0; \ + } \ + \ + *i = x; \ + return 0; \ + } diff --git a/src/shared/dbus-common.c b/src/shared/dbus-common.c new file mode 100644 index 0000000000..b8c15cb9fc --- /dev/null +++ b/src/shared/dbus-common.c @@ -0,0 +1,1428 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <assert.h> +#include <sys/socket.h> +#include <errno.h> +#include <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <dbus/dbus.h> +#include <string.h> +#include <sys/epoll.h> + +#include "log.h" +#include "dbus-common.h" +#include "util.h" +#include "missing.h" +#include "def.h" +#include "strv.h" + +int bus_check_peercred(DBusConnection *c) { + int fd; + struct ucred ucred; + socklen_t l; + + assert(c); + + assert_se(dbus_connection_get_unix_fd(c, &fd)); + + l = sizeof(struct ucred); + if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &ucred, &l) < 0) { + log_error("SO_PEERCRED failed: %m"); + return -errno; + } + + if (l != sizeof(struct ucred)) { + log_error("SO_PEERCRED returned wrong size."); + return -E2BIG; + } + + if (ucred.uid != 0 && ucred.uid != geteuid()) + return -EPERM; + + return 1; +} + +static int sync_auth(DBusConnection *bus, DBusError *error) { + usec_t begin, tstamp; + + assert(bus); + + /* This complexity should probably move into D-Bus itself: + * + * https://bugs.freedesktop.org/show_bug.cgi?id=35189 */ + + begin = tstamp = now(CLOCK_MONOTONIC); + for (;;) { + + if (tstamp > begin + DEFAULT_TIMEOUT_USEC) + break; + + if (dbus_connection_get_is_authenticated(bus)) + break; + + if (!dbus_connection_read_write_dispatch(bus, ((begin + DEFAULT_TIMEOUT_USEC - tstamp) + USEC_PER_MSEC - 1) / USEC_PER_MSEC)) + break; + + tstamp = now(CLOCK_MONOTONIC); + } + + if (!dbus_connection_get_is_connected(bus)) { + dbus_set_error_const(error, DBUS_ERROR_NO_SERVER, "Connection terminated during authentication."); + return -ECONNREFUSED; + } + + if (!dbus_connection_get_is_authenticated(bus)) { + dbus_set_error_const(error, DBUS_ERROR_TIMEOUT, "Failed to authenticate in time."); + return -EACCES; + } + + return 0; +} + +int bus_connect(DBusBusType t, DBusConnection **_bus, bool *_private, DBusError *error) { + DBusConnection *bus = NULL; + int r; + bool private = true; + + assert(_bus); + + if (geteuid() == 0 && t == DBUS_BUS_SYSTEM) { + /* If we are root, then let's talk directly to the + * system instance, instead of going via the bus */ + + bus = dbus_connection_open_private("unix:path=/run/systemd/private", error); + if (!bus) + return -EIO; + + } else { + if (t == DBUS_BUS_SESSION) { + const char *e; + + /* If we are supposed to talk to the instance, + * try via XDG_RUNTIME_DIR first, then + * fallback to normal bus access */ + + e = secure_getenv("XDG_RUNTIME_DIR"); + if (e) { + char *p; + + if (asprintf(&p, "unix:path=%s/systemd/private", e) < 0) + return -ENOMEM; + + bus = dbus_connection_open_private(p, NULL); + free(p); + } + } + + if (!bus) { + bus = dbus_bus_get_private(t, error); + if (!bus) + return -EIO; + + private = false; + } + } + + dbus_connection_set_exit_on_disconnect(bus, FALSE); + + if (private) { + if (bus_check_peercred(bus) < 0) { + dbus_connection_close(bus); + dbus_connection_unref(bus); + + dbus_set_error_const(error, DBUS_ERROR_ACCESS_DENIED, "Failed to verify owner of bus."); + return -EACCES; + } + } + + r = sync_auth(bus, error); + if (r < 0) { + dbus_connection_close(bus); + dbus_connection_unref(bus); + return r; + } + + if (_private) + *_private = private; + + *_bus = bus; + return 0; +} + +int bus_connect_system_ssh(const char *user, const char *host, DBusConnection **_bus, DBusError *error) { + DBusConnection *bus; + char *p = NULL; + int r; + + assert(_bus); + assert(user || host); + + if (user && host) + asprintf(&p, "unixexec:path=ssh,argv1=-xT,argv2=%s@%s,argv3=systemd-stdio-bridge", user, host); + else if (user) + asprintf(&p, "unixexec:path=ssh,argv1=-xT,argv2=%s@localhost,argv3=systemd-stdio-bridge", user); + else if (host) + asprintf(&p, "unixexec:path=ssh,argv1=-xT,argv2=%s,argv3=systemd-stdio-bridge", host); + + if (!p) { + dbus_set_error_const(error, DBUS_ERROR_NO_MEMORY, NULL); + return -ENOMEM; + } + + bus = dbus_connection_open_private(p, error); + free(p); + + if (!bus) + return -EIO; + + dbus_connection_set_exit_on_disconnect(bus, FALSE); + + if ((r = sync_auth(bus, error)) < 0) { + dbus_connection_close(bus); + dbus_connection_unref(bus); + return r; + } + + if (!dbus_bus_register(bus, error)) { + dbus_connection_close(bus); + dbus_connection_unref(bus); + return r; + } + + *_bus = bus; + return 0; +} + +int bus_connect_system_polkit(DBusConnection **_bus, DBusError *error) { + DBusConnection *bus; + int r; + + assert(_bus); + + /* Don't bother with PolicyKit if we are root */ + if (geteuid() == 0) + return bus_connect(DBUS_BUS_SYSTEM, _bus, NULL, error); + + bus = dbus_connection_open_private("unixexec:path=pkexec,argv1=" SYSTEMD_STDIO_BRIDGE_BINARY_PATH, error); + if (!bus) + return -EIO; + + dbus_connection_set_exit_on_disconnect(bus, FALSE); + + r = sync_auth(bus, error); + if (r < 0) { + dbus_connection_close(bus); + dbus_connection_unref(bus); + return r; + } + + if (!dbus_bus_register(bus, error)) { + dbus_connection_close(bus); + dbus_connection_unref(bus); + return r; + } + + *_bus = bus; + return 0; +} + +const char *bus_error_message(const DBusError *error) { + if (!error) + return NULL; + + /* Sometimes the D-Bus server is a little bit too verbose with + * its error messages, so let's override them here */ + if (dbus_error_has_name(error, DBUS_ERROR_ACCESS_DENIED)) + return "Access denied"; + + return error->message; +} + +const char *bus_error(const DBusError *error, int err) { + if (error && dbus_error_is_set(error)) + return bus_error_message(error); + + return strerror(err < 0 ? -err : err); +} + +DBusHandlerResult bus_default_message_handler( + DBusConnection *c, + DBusMessage *message, + const char *introspection, + const char *interfaces, + const BusBoundProperties *bound_properties) { + + DBusError error; + _cleanup_dbus_message_unref_ DBusMessage *reply = NULL; + int r; + + assert(c); + assert(message); + + dbus_error_init(&error); + + if (dbus_message_is_method_call(message, "org.freedesktop.DBus.Introspectable", "Introspect") && introspection) { + + reply = dbus_message_new_method_return(message); + if (!reply) + goto oom; + + if (!dbus_message_append_args(reply, DBUS_TYPE_STRING, &introspection, DBUS_TYPE_INVALID)) + goto oom; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.DBus.Properties", "Get") && bound_properties) { + const char *interface, *property; + const BusBoundProperties *bp; + const BusProperty *p; + void *data; + DBusMessageIter iter, sub; + + if (!dbus_message_get_args( + message, + &error, + DBUS_TYPE_STRING, &interface, + DBUS_TYPE_STRING, &property, + DBUS_TYPE_INVALID)) + return bus_send_error_reply(c, message, &error, -EINVAL); + + for (bp = bound_properties; bp->interface; bp++) { + if (!streq(bp->interface, interface)) + continue; + + for (p = bp->properties; p->property; p++) + if (streq(p->property, property)) + goto get_prop; + } + + /* no match */ + if (!nulstr_contains(interfaces, interface)) + dbus_set_error_const(&error, DBUS_ERROR_UNKNOWN_INTERFACE, "Unknown interface"); + else + dbus_set_error_const(&error, DBUS_ERROR_UNKNOWN_PROPERTY, "Unknown property"); + + return bus_send_error_reply(c, message, &error, -EINVAL); + +get_prop: + reply = dbus_message_new_method_return(message); + if (!reply) + goto oom; + + dbus_message_iter_init_append(reply, &iter); + + if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT, p->signature, &sub)) + goto oom; + + data = (char*)bp->base + p->offset; + if (p->indirect) + data = *(void**)data; + + r = p->append(&sub, property, data); + if (r == -ENOMEM) + goto oom; + if (r < 0) + return bus_send_error_reply(c, message, NULL, r); + + if (!dbus_message_iter_close_container(&iter, &sub)) + goto oom; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.DBus.Properties", "GetAll") && bound_properties) { + const char *interface; + const BusBoundProperties *bp; + const BusProperty *p; + DBusMessageIter iter, sub, sub2, sub3; + + if (!dbus_message_get_args( + message, + &error, + DBUS_TYPE_STRING, &interface, + DBUS_TYPE_INVALID)) + return bus_send_error_reply(c, message, &error, -EINVAL); + + if (interface[0] && !nulstr_contains(interfaces, interface)) { + dbus_set_error_const(&error, DBUS_ERROR_UNKNOWN_INTERFACE, "Unknown interface"); + return bus_send_error_reply(c, message, &error, -EINVAL); + } + + reply = dbus_message_new_method_return(message); + if (!reply) + goto oom; + + dbus_message_iter_init_append(reply, &iter); + + if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub)) + goto oom; + + for (bp = bound_properties; bp->interface; bp++) { + if (interface[0] && !streq(bp->interface, interface)) + continue; + + for (p = bp->properties; p->property; p++) { + void *data; + + if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_DICT_ENTRY, NULL, &sub2) || + !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &p->property) || + !dbus_message_iter_open_container(&sub2, DBUS_TYPE_VARIANT, p->signature, &sub3)) + goto oom; + + data = (char*)bp->base + p->offset; + if (p->indirect) + data = *(void**)data; + r = p->append(&sub3, p->property, data); + if (r == -ENOMEM) + goto oom; + if (r < 0) + return bus_send_error_reply(c, message, NULL, r); + + if (!dbus_message_iter_close_container(&sub2, &sub3) || + !dbus_message_iter_close_container(&sub, &sub2)) + goto oom; + } + } + + if (!dbus_message_iter_close_container(&iter, &sub)) + goto oom; + + } else if (dbus_message_is_method_call(message, "org.freedesktop.DBus.Properties", "Set") && bound_properties) { + const char *interface, *property; + DBusMessageIter iter; + const BusBoundProperties *bp; + const BusProperty *p; + DBusMessageIter sub; + char *sig; + void *data; + DBusMessage *changed; + + if (!dbus_message_iter_init(message, &iter) || + dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) + return bus_send_error_reply(c, message, NULL, -EINVAL); + + dbus_message_iter_get_basic(&iter, &interface); + + if (!dbus_message_iter_next(&iter) || + dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) + return bus_send_error_reply(c, message, NULL, -EINVAL); + + dbus_message_iter_get_basic(&iter, &property); + + if (!dbus_message_iter_next(&iter) || + dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT || + dbus_message_iter_has_next(&iter)) + return bus_send_error_reply(c, message, NULL, -EINVAL); + + for (bp = bound_properties; bp->interface; bp++) { + if (!streq(bp->interface, interface)) + continue; + + for (p = bp->properties; p->property; p++) + if (streq(p->property, property)) + goto set_prop; + } + + /* no match */ + if (!nulstr_contains(interfaces, interface)) + dbus_set_error_const(&error, DBUS_ERROR_UNKNOWN_INTERFACE, "Unknown interface"); + else + dbus_set_error_const(&error, DBUS_ERROR_UNKNOWN_PROPERTY, "Unknown property"); + + return bus_send_error_reply(c, message, &error, -EINVAL); + +set_prop: + if (!p->set) { + dbus_set_error_const(&error, DBUS_ERROR_PROPERTY_READ_ONLY, "Property read-only"); + return bus_send_error_reply(c, message, &error, -EINVAL); + } + + dbus_message_iter_recurse(&iter, &sub); + + sig = dbus_message_iter_get_signature(&sub); + if (!sig) + goto oom; + + if (!streq(sig, p->signature)) { + dbus_free(sig); + return bus_send_error_reply(c, message, NULL, -EINVAL); + } + dbus_free(sig); + + data = (uint8_t*) bp->base + p->offset; + if (p->indirect) + data = *(void**)data; + + r = p->set(&sub, property, data); + if (r == -ENOMEM) + goto oom; + else if (r < 0) + return bus_send_error_reply(c, message, NULL, r); + + reply = dbus_message_new_method_return(message); + if (!reply) + goto oom; + + /* Send out a signal about this, but it doesn't really + * matter if this fails, so eat all errors */ + changed = bus_properties_changed_one_new( + dbus_message_get_path(message), + interface, + property); + if (changed) { + dbus_connection_send(c, changed, NULL); + dbus_message_unref(changed); + } + + + } else { + const char *interface = dbus_message_get_interface(message); + + if (!interface || !nulstr_contains(interfaces, interface)) { + dbus_set_error_const(&error, DBUS_ERROR_UNKNOWN_INTERFACE, "Unknown interface"); + return bus_send_error_reply(c, message, &error, -EINVAL); + } + } + + if (reply) { + if (!bus_maybe_send_reply(c, message, reply)) + goto oom; + + return DBUS_HANDLER_RESULT_HANDLED; + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + +oom: + dbus_error_free(&error); + + return DBUS_HANDLER_RESULT_NEED_MEMORY; +} + +int bus_property_append_string(DBusMessageIter *i, const char *property, void *data) { + const char *t = data; + + assert(i); + assert(property); + + if (!t) + t = ""; + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &t)) + return -ENOMEM; + + return 0; +} + +int bus_property_append_strv(DBusMessageIter *i, const char *property, void *data) { + char **t = data; + + assert(i); + assert(property); + + return bus_append_strv_iter(i, t); +} + +int bus_property_append_bool(DBusMessageIter *i, const char *property, void *data) { + bool *b = data; + dbus_bool_t db; + + assert(i); + assert(property); + assert(b); + + db = *b; + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &db)) + return -ENOMEM; + + return 0; +} + +int bus_property_append_tristate_false(DBusMessageIter *i, const char *property, void *data) { + int *b = data; + dbus_bool_t db; + + assert(i); + assert(property); + assert(b); + + db = *b > 0; + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &db)) + return -ENOMEM; + + return 0; +} + +int bus_property_append_uint64(DBusMessageIter *i, const char *property, void *data) { + assert(i); + assert(property); + assert(data); + + /* Let's ensure that usec_t is actually 64bit, and hence this + * function can be used for usec_t */ + assert_cc(sizeof(uint64_t) == sizeof(usec_t)); + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_UINT64, data)) + return -ENOMEM; + + return 0; +} + +int bus_property_append_uint32(DBusMessageIter *i, const char *property, void *data) { + assert(i); + assert(property); + assert(data); + + /* Let's ensure that pid_t, mode_t, uid_t, gid_t are actually + * 32bit, and hence this function can be used for + * pid_t/mode_t/uid_t/gid_t */ + assert_cc(sizeof(uint32_t) == sizeof(pid_t)); + assert_cc(sizeof(uint32_t) == sizeof(mode_t)); + assert_cc(sizeof(uint32_t) == sizeof(unsigned)); + assert_cc(sizeof(uint32_t) == sizeof(uid_t)); + assert_cc(sizeof(uint32_t) == sizeof(gid_t)); + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_UINT32, data)) + return -ENOMEM; + + return 0; +} + +int bus_property_append_int32(DBusMessageIter *i, const char *property, void *data) { + assert(i); + assert(property); + assert(data); + + assert_cc(sizeof(int32_t) == sizeof(int)); + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_INT32, data)) + return -ENOMEM; + + return 0; +} + +int bus_property_append_size(DBusMessageIter *i, const char *property, void *data) { + uint64_t u; + + assert(i); + assert(property); + assert(data); + + u = (uint64_t) *(size_t*) data; + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_UINT64, &u)) + return -ENOMEM; + + return 0; +} + +int bus_property_append_ul(DBusMessageIter *i, const char *property, void *data) { + uint64_t u; + + assert(i); + assert(property); + assert(data); + + u = (uint64_t) *(unsigned long*) data; + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_UINT64, &u)) + return -ENOMEM; + + return 0; +} + +int bus_property_append_long(DBusMessageIter *i, const char *property, void *data) { + int64_t l; + + assert(i); + assert(property); + assert(data); + + l = (int64_t) *(long*) data; + + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_INT64, &l)) + return -ENOMEM; + + return 0; +} + +int bus_property_set_uint64(DBusMessageIter *i, const char *property, void *data) { + uint64_t *t = data; + + assert(i); + assert(property); + + dbus_message_iter_get_basic(i, t); + return 0; +} + +const char *bus_errno_to_dbus(int error) { + + switch(error) { + + case -EINVAL: + return DBUS_ERROR_INVALID_ARGS; + + case -ENOMEM: + return DBUS_ERROR_NO_MEMORY; + + case -EPERM: + case -EACCES: + return DBUS_ERROR_ACCESS_DENIED; + + case -ESRCH: + return DBUS_ERROR_UNIX_PROCESS_ID_UNKNOWN; + + case -ENOENT: + return DBUS_ERROR_FILE_NOT_FOUND; + + case -EEXIST: + return DBUS_ERROR_FILE_EXISTS; + + case -ETIMEDOUT: + case -ETIME: + return DBUS_ERROR_TIMEOUT; + + case -EIO: + return DBUS_ERROR_IO_ERROR; + + case -ENETRESET: + case -ECONNABORTED: + case -ECONNRESET: + return DBUS_ERROR_DISCONNECTED; + } + + return DBUS_ERROR_FAILED; +} + +dbus_bool_t bus_maybe_send_reply (DBusConnection *c, + DBusMessage *message, + DBusMessage *reply) +{ + /* Some parts of systemd "reply" to signals, which of course + * have the no-reply flag set. We will be defensive here and + * still send out a reply if we're passed a signal. + */ + if (dbus_message_get_type(message) == DBUS_MESSAGE_TYPE_METHOD_CALL && + dbus_message_get_no_reply(message)) + return TRUE; + return dbus_connection_send(c, reply, NULL); +} + +DBusHandlerResult bus_send_error_reply(DBusConnection *c, DBusMessage *message, DBusError *berror, int error) { + _cleanup_dbus_message_unref_ DBusMessage *reply = NULL; + const char *name, *text; + + if (berror && dbus_error_is_set(berror)) { + name = berror->name; + text = berror->message; + } else { + name = bus_errno_to_dbus(error); + text = strerror(-error); + } + + reply = dbus_message_new_error(message, name, text); + if (!reply) + goto oom; + + if (!bus_maybe_send_reply(c, message, reply)) + goto oom; + + if (berror) + dbus_error_free(berror); + + return DBUS_HANDLER_RESULT_HANDLED; + +oom: + if (reply) + dbus_message_unref(reply); + + if (berror) + dbus_error_free(berror); + + return DBUS_HANDLER_RESULT_NEED_MEMORY; +} + +DBusMessage* bus_properties_changed_new(const char *path, const char *interface, const char *properties) { + DBusMessage *m; + DBusMessageIter iter, sub; + const char *i; + + assert(interface); + assert(properties); + + m = dbus_message_new_signal(path, "org.freedesktop.DBus.Properties", "PropertiesChanged"); + if (!m) + goto oom; + + dbus_message_iter_init_append(m, &iter); + + /* We won't send any property values, since they might be + * large and sometimes not cheap to generated */ + + if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &interface) || + !dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub) || + !dbus_message_iter_close_container(&iter, &sub) || + !dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "s", &sub)) + goto oom; + + NULSTR_FOREACH(i, properties) + if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &i)) + goto oom; + + if (!dbus_message_iter_close_container(&iter, &sub)) + goto oom; + + return m; + +oom: + if (m) + dbus_message_unref(m); + + return NULL; +} + +DBusMessage* bus_properties_changed_one_new(const char *path, const char *interface, const char *property) { + DBusMessage *m; + DBusMessageIter iter, sub; + + assert(interface); + assert(property); + + m = dbus_message_new_signal(path, "org.freedesktop.DBus.Properties", "PropertiesChanged"); + if (!m) + goto oom; + + dbus_message_iter_init_append(m, &iter); + + /* We won't send any property values, since they might be + * large and sometimes not cheap to generated */ + + if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &interface) || + !dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub) || + !dbus_message_iter_close_container(&iter, &sub) || + !dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "s", &sub)) + goto oom; + + if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &property)) + goto oom; + + if (!dbus_message_iter_close_container(&iter, &sub)) + goto oom; + + return m; + +oom: + if (m) + dbus_message_unref(m); + + return NULL; +} + +uint32_t bus_flags_to_events(DBusWatch *bus_watch) { + unsigned flags; + uint32_t events = 0; + + assert(bus_watch); + + /* no watch flags for disabled watches */ + if (!dbus_watch_get_enabled(bus_watch)) + return 0; + + flags = dbus_watch_get_flags(bus_watch); + + if (flags & DBUS_WATCH_READABLE) + events |= EPOLLIN; + if (flags & DBUS_WATCH_WRITABLE) + events |= EPOLLOUT; + + return events | EPOLLHUP | EPOLLERR; +} + +unsigned bus_events_to_flags(uint32_t events) { + unsigned flags = 0; + + if (events & EPOLLIN) + flags |= DBUS_WATCH_READABLE; + if (events & EPOLLOUT) + flags |= DBUS_WATCH_WRITABLE; + if (events & EPOLLHUP) + flags |= DBUS_WATCH_HANGUP; + if (events & EPOLLERR) + flags |= DBUS_WATCH_ERROR; + + return flags; +} + +int bus_parse_strv(DBusMessage *m, char ***_l) { + DBusMessageIter iter; + + assert(m); + assert(_l); + + if (!dbus_message_iter_init(m, &iter)) + return -EINVAL; + + return bus_parse_strv_iter(&iter, _l); +} + +int bus_parse_strv_iter(DBusMessageIter *iter, char ***_l) { + DBusMessageIter sub; + unsigned n = 0, i = 0; + char **l; + + assert(iter); + assert(_l); + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY || + dbus_message_iter_get_element_type(iter) != DBUS_TYPE_STRING) + return -EINVAL; + + dbus_message_iter_recurse(iter, &sub); + + while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) { + n++; + dbus_message_iter_next(&sub); + } + + l = new(char*, n+1); + if (!l) + return -ENOMEM; + + dbus_message_iter_recurse(iter, &sub); + + while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) { + const char *s; + + assert_se(dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING); + dbus_message_iter_get_basic(&sub, &s); + + if (!(l[i++] = strdup(s))) { + strv_free(l); + return -ENOMEM; + } + + dbus_message_iter_next(&sub); + } + + assert(i == n); + l[i] = NULL; + + if (_l) + *_l = l; + + return 0; +} + +int bus_parse_strv_pairs_iter(DBusMessageIter *iter, char ***_l) { + DBusMessageIter sub, sub2; + unsigned n = 0, i = 0; + char **l; + + assert(iter); + assert(_l); + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY || + dbus_message_iter_get_element_type(iter) != DBUS_TYPE_STRUCT) + return -EINVAL; + + dbus_message_iter_recurse(iter, &sub); + + while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) { + n++; + dbus_message_iter_next(&sub); + } + + l = new(char*, n*2+1); + if (!l) + return -ENOMEM; + + dbus_message_iter_recurse(iter, &sub); + + while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) { + const char *a, *b; + + assert_se(dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRUCT); + + dbus_message_iter_recurse(&sub, &sub2); + + if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &a, true) < 0 || + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &b, false) < 0) + return -EINVAL; + + l[i] = strdup(a); + if (!l[i]) { + strv_free(l); + return -ENOMEM; + } + + l[++i] = strdup(b); + if (!l[i]) { + strv_free(l); + return -ENOMEM; + } + + i++; + dbus_message_iter_next(&sub); + } + + assert(i == n*2); + l[i] = NULL; + + if (_l) + *_l = l; + + return 0; +} + +int bus_parse_unit_info(DBusMessageIter *iter, struct unit_info *u) { + DBusMessageIter sub; + + assert(iter); + assert(u); + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRUCT) + return -EINVAL; + + dbus_message_iter_recurse(iter, &sub); + + if (bus_iter_get_basic_and_next(&sub, DBUS_TYPE_STRING, &u->id, true) < 0 || + bus_iter_get_basic_and_next(&sub, DBUS_TYPE_STRING, &u->description, true) < 0 || + bus_iter_get_basic_and_next(&sub, DBUS_TYPE_STRING, &u->load_state, true) < 0 || + bus_iter_get_basic_and_next(&sub, DBUS_TYPE_STRING, &u->active_state, true) < 0 || + bus_iter_get_basic_and_next(&sub, DBUS_TYPE_STRING, &u->sub_state, true) < 0 || + bus_iter_get_basic_and_next(&sub, DBUS_TYPE_STRING, &u->following, true) < 0 || + bus_iter_get_basic_and_next(&sub, DBUS_TYPE_OBJECT_PATH, &u->unit_path, true) < 0 || + bus_iter_get_basic_and_next(&sub, DBUS_TYPE_UINT32, &u->job_id, true) < 0 || + bus_iter_get_basic_and_next(&sub, DBUS_TYPE_STRING, &u->job_type, true) < 0 || + bus_iter_get_basic_and_next(&sub, DBUS_TYPE_OBJECT_PATH, &u->job_path, false) < 0) { + log_error("Failed to parse reply."); + return -EIO; + } + + return 0; +} + +int bus_append_strv_iter(DBusMessageIter *iter, char **l) { + DBusMessageIter sub; + + assert(iter); + + if (!dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "s", &sub)) + return -ENOMEM; + + STRV_FOREACH(l, l) + if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, l)) + return -ENOMEM; + + if (!dbus_message_iter_close_container(iter, &sub)) + return -ENOMEM; + + return 0; +} + +int bus_iter_get_basic_and_next(DBusMessageIter *iter, int type, void *data, bool next) { + + assert(iter); + assert(data); + + if (dbus_message_iter_get_arg_type(iter) != type) + return -EIO; + + dbus_message_iter_get_basic(iter, data); + + if (!dbus_message_iter_next(iter) != !next) + return -EIO; + + return 0; +} + +int generic_print_property(const char *name, DBusMessageIter *iter, bool all) { + assert(name); + assert(iter); + + switch (dbus_message_iter_get_arg_type(iter)) { + + case DBUS_TYPE_STRING: { + const char *s; + dbus_message_iter_get_basic(iter, &s); + + if (all || !isempty(s)) + printf("%s=%s\n", name, s); + + return 1; + } + + case DBUS_TYPE_BOOLEAN: { + dbus_bool_t b; + + dbus_message_iter_get_basic(iter, &b); + printf("%s=%s\n", name, yes_no(b)); + + return 1; + } + + case DBUS_TYPE_UINT64: { + uint64_t u; + dbus_message_iter_get_basic(iter, &u); + + /* Yes, heuristics! But we can change this check + * should it turn out to not be sufficient */ + + if (endswith(name, "Timestamp")) { + char timestamp[FORMAT_TIMESTAMP_MAX], *t; + + t = format_timestamp(timestamp, sizeof(timestamp), u); + if (t || all) + printf("%s=%s\n", name, strempty(t)); + + } else if (strstr(name, "USec")) { + char timespan[FORMAT_TIMESPAN_MAX]; + + printf("%s=%s\n", name, format_timespan(timespan, sizeof(timespan), u, 0)); + } else + printf("%s=%llu\n", name, (unsigned long long) u); + + return 1; + } + + case DBUS_TYPE_UINT32: { + uint32_t u; + dbus_message_iter_get_basic(iter, &u); + + if (strstr(name, "UMask") || strstr(name, "Mode")) + printf("%s=%04o\n", name, u); + else + printf("%s=%u\n", name, (unsigned) u); + + return 1; + } + + case DBUS_TYPE_INT32: { + int32_t i; + dbus_message_iter_get_basic(iter, &i); + + printf("%s=%i\n", name, (int) i); + return 1; + } + + case DBUS_TYPE_DOUBLE: { + double d; + dbus_message_iter_get_basic(iter, &d); + + printf("%s=%g\n", name, d); + return 1; + } + + case DBUS_TYPE_ARRAY: + + if (dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRING) { + DBusMessageIter sub; + bool space = false; + + dbus_message_iter_recurse(iter, &sub); + if (all || + dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) { + printf("%s=", name); + + while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) { + const char *s; + + assert(dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING); + dbus_message_iter_get_basic(&sub, &s); + printf("%s%s", space ? " " : "", s); + + space = true; + dbus_message_iter_next(&sub); + } + + puts(""); + } + + return 1; + + } else if (dbus_message_iter_get_element_type(iter) == DBUS_TYPE_BYTE) { + DBusMessageIter sub; + + dbus_message_iter_recurse(iter, &sub); + if (all || + dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) { + printf("%s=", name); + + while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) { + uint8_t u; + + assert(dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_BYTE); + dbus_message_iter_get_basic(&sub, &u); + printf("%02x", u); + + dbus_message_iter_next(&sub); + } + + puts(""); + } + + return 1; + + } else if (dbus_message_iter_get_element_type(iter) == DBUS_TYPE_UINT32) { + DBusMessageIter sub; + + dbus_message_iter_recurse(iter, &sub); + if (all || + dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) { + printf("%s=", name); + + while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) { + uint32_t u; + + assert(dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_UINT32); + dbus_message_iter_get_basic(&sub, &u); + printf("%08x", u); + + dbus_message_iter_next(&sub); + } + + puts(""); + } + + return 1; + } + + break; + } + + return 0; +} + +static void release_name_pending_cb(DBusPendingCall *pending, void *userdata) { + DBusMessage *reply; + DBusConnection *bus = userdata; + + assert_se(reply = dbus_pending_call_steal_reply(pending)); + dbus_message_unref(reply); + + dbus_connection_close(bus); +} + +void bus_async_unregister_and_exit(DBusConnection *bus, const char *name) { + _cleanup_dbus_message_unref_ DBusMessage *m = NULL; + DBusPendingCall *pending = NULL; + + assert(bus); + + /* We unregister the name here, but we continue to process + * requests, until we get the response for it, so that all + * requests are guaranteed to be processed. */ + + m = dbus_message_new_method_call( + DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS, + "ReleaseName"); + if (!m) + goto oom; + + if (!dbus_message_append_args( + m, + DBUS_TYPE_STRING, + &name, + DBUS_TYPE_INVALID)) + goto oom; + + if (!dbus_connection_send_with_reply(bus, m, &pending, -1)) + goto oom; + + if (!dbus_pending_call_set_notify(pending, release_name_pending_cb, bus, NULL)) + goto oom; + + dbus_pending_call_unref(pending); + + return; + +oom: + log_oom(); + + if (pending) { + dbus_pending_call_cancel(pending); + dbus_pending_call_unref(pending); + } +} + +DBusHandlerResult bus_exit_idle_filter(DBusConnection *bus, DBusMessage *m, void *userdata) { + usec_t *remain_until = userdata; + + assert(bus); + assert(m); + assert(remain_until); + + /* Every time we get a new message we reset out timeout */ + *remain_until = now(CLOCK_MONOTONIC) + DEFAULT_EXIT_USEC; + + if (dbus_message_is_signal(m, DBUS_INTERFACE_LOCAL, "Disconnected")) + dbus_connection_close(bus); + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +/* This mimics dbus_bus_get_unix_user() */ +pid_t bus_get_unix_process_id( + DBusConnection *connection, + const char *name, + DBusError *error) { + + _cleanup_dbus_message_unref_ DBusMessage *m = NULL, *reply = NULL; + uint32_t pid = 0; + + m = dbus_message_new_method_call( + DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS, + "GetConnectionUnixProcessID"); + if (!m) { + dbus_set_error_const(error, DBUS_ERROR_NO_MEMORY, NULL); + return 0; + } + + if (!dbus_message_append_args( + m, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID)) { + dbus_set_error_const(error, DBUS_ERROR_NO_MEMORY, NULL); + return 0; + } + + reply = dbus_connection_send_with_reply_and_block(connection, m, -1, error); + if (!reply) + return 0; + + if (dbus_set_error_from_message(error, reply)) + return 0; + + if (!dbus_message_get_args( + reply, error, + DBUS_TYPE_UINT32, &pid, + DBUS_TYPE_INVALID)) + return 0; + + return (pid_t) pid; +} + +bool bus_error_is_no_service(const DBusError *error) { + assert(error); + + if (!dbus_error_is_set(error)) + return false; + + if (dbus_error_has_name(error, DBUS_ERROR_NAME_HAS_NO_OWNER)) + return true; + + if (dbus_error_has_name(error, DBUS_ERROR_SERVICE_UNKNOWN)) + return true; + + return startswith(error->name, "org.freedesktop.DBus.Error.Spawn."); +} + +int bus_method_call_with_reply( + DBusConnection *bus, + const char *destination, + const char *path, + const char *interface, + const char *method, + DBusMessage **return_reply, + DBusError *return_error, + int first_arg_type, ...) { + + DBusError error; + _cleanup_dbus_message_unref_ DBusMessage *m = NULL; + DBusMessage *reply; + va_list ap; + int r = 0; + + dbus_error_init(&error); + assert(bus); + + m = dbus_message_new_method_call(destination, path, interface, method); + if (!m) { + r = log_oom(); + goto finish; + } + + va_start(ap, first_arg_type); + if (!dbus_message_append_args_valist(m, first_arg_type, ap)) { + va_end(ap); + r = log_oom(); + goto finish; + } + va_end(ap); + + reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error); + if (!reply) { + if (!return_error) + log_error("Failed to issue method call: %s", bus_error_message(&error)); + + if (bus_error_is_no_service(&error)) + r = -ENOENT; + else if (dbus_error_has_name(&error, DBUS_ERROR_ACCESS_DENIED)) + r = -EACCES; + else if (dbus_error_has_name(&error, DBUS_ERROR_NO_REPLY)) + r = -ETIMEDOUT; + else + r = -EIO; + goto finish; + } + + if (return_reply) + *return_reply = reply; + else + dbus_message_unref(reply); + +finish: + if (return_error) + *return_error = error; + else + dbus_error_free(&error); + + return r; +} + +void bus_message_unrefp(DBusMessage **reply) { + if (!reply) + return; + + if (!*reply) + return; + + dbus_message_unref(*reply); +} + +const char *bus_message_get_sender_with_fallback(DBusMessage *m) { + const char *s; + + assert(m); + + s = dbus_message_get_sender(m); + if (s) + return s; + + /* When the message came in from a direct connection the + * message will have no sender. We fix that here. */ + + return ":no-sender"; +} diff --git a/src/shared/dbus-common.h b/src/shared/dbus-common.h new file mode 100644 index 0000000000..9752f08c05 --- /dev/null +++ b/src/shared/dbus-common.h @@ -0,0 +1,246 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <dbus/dbus.h> +#include <inttypes.h> +#include <sys/types.h> + +#include "macro.h" + +#ifndef DBUS_ERROR_UNKNOWN_OBJECT +#define DBUS_ERROR_UNKNOWN_OBJECT "org.freedesktop.DBus.Error.UnknownObject" +#endif + +#ifndef DBUS_ERROR_UNKNOWN_INTERFACE +#define DBUS_ERROR_UNKNOWN_INTERFACE "org.freedesktop.DBus.Error.UnknownInterface" +#endif + +#ifndef DBUS_ERROR_UNKNOWN_PROPERTY +#define DBUS_ERROR_UNKNOWN_PROPERTY "org.freedesktop.DBus.Error.UnknownProperty" +#endif + +#ifndef DBUS_ERROR_PROPERTY_READ_ONLY +#define DBUS_ERROR_PROPERTY_READ_ONLY "org.freedesktop.DBus.Error.PropertyReadOnly" +#endif + +#define BUS_PROPERTIES_INTERFACE \ + " <interface name=\"org.freedesktop.DBus.Properties\">\n" \ + " <method name=\"Get\">\n" \ + " <arg name=\"interface\" direction=\"in\" type=\"s\"/>\n" \ + " <arg name=\"property\" direction=\"in\" type=\"s\"/>\n" \ + " <arg name=\"value\" direction=\"out\" type=\"v\"/>\n" \ + " </method>\n" \ + " <method name=\"GetAll\">\n" \ + " <arg name=\"interface\" direction=\"in\" type=\"s\"/>\n" \ + " <arg name=\"properties\" direction=\"out\" type=\"a{sv}\"/>\n" \ + " </method>\n" \ + " <method name=\"Set\">\n" \ + " <arg name=\"interface\" direction=\"in\" type=\"s\"/>\n" \ + " <arg name=\"property\" direction=\"in\" type=\"s\"/>\n" \ + " <arg name=\"value\" direction=\"in\" type=\"v\"/>\n" \ + " </method>\n" \ + " <signal name=\"PropertiesChanged\">\n" \ + " <arg type=\"s\" name=\"interface\"/>\n" \ + " <arg type=\"a{sv}\" name=\"changed_properties\"/>\n" \ + " <arg type=\"as\" name=\"invalidated_properties\"/>\n" \ + " </signal>\n" \ + " </interface>\n" + +#define BUS_INTROSPECTABLE_INTERFACE \ + " <interface name=\"org.freedesktop.DBus.Introspectable\">\n" \ + " <method name=\"Introspect\">\n" \ + " <arg name=\"data\" type=\"s\" direction=\"out\"/>\n" \ + " </method>\n" \ + " </interface>\n" + +#define BUS_PEER_INTERFACE \ + "<interface name=\"org.freedesktop.DBus.Peer\">\n" \ + " <method name=\"Ping\"/>\n" \ + " <method name=\"GetMachineId\">\n" \ + " <arg type=\"s\" name=\"machine_uuid\" direction=\"out\"/>\n" \ + " </method>\n" \ + "</interface>\n" + +#define BUS_GENERIC_INTERFACES_LIST \ + "org.freedesktop.DBus.Properties\0" \ + "org.freedesktop.DBus.Introspectable\0" \ + "org.freedesktop.DBus.Peer\0" + +int bus_check_peercred(DBusConnection *c); + +int bus_connect(DBusBusType t, DBusConnection **_bus, bool *private_bus, DBusError *error); + +int bus_connect_system_ssh(const char *user, const char *host, DBusConnection **_bus, DBusError *error); +int bus_connect_system_polkit(DBusConnection **_bus, DBusError *error); + +const char *bus_error_message(const DBusError *error); +const char *bus_error(const DBusError *e, int r); + +typedef int (*BusPropertyCallback)(DBusMessageIter *iter, const char *property, void *data); +typedef int (*BusPropertySetCallback)(DBusMessageIter *iter, const char *property, void *data); + +typedef struct BusProperty { + const char *property; /* name of the property */ + BusPropertyCallback append; /* Function that is called to serialize this property */ + const char *signature; + const uint16_t offset; /* Offset from BusBoundProperties::base address to the property data. + * uint16_t is sufficient, because we have no structs too big. + * -Werror=overflow will catch it if this does not hold. */ + bool indirect; /* data is indirect, ie. not base+offset, but *(base+offset) */ + BusPropertySetCallback set; /* Optional: Function that is called to set this property */ +} BusProperty; + +typedef struct BusBoundProperties { + const char *interface; /* interface of the properties */ + const BusProperty *properties; /* array of properties, ended by a NULL-filled element */ + const void *const base; /* base pointer to which the offset must be added to reach data */ +} BusBoundProperties; + +dbus_bool_t bus_maybe_send_reply (DBusConnection *c, + DBusMessage *message, + DBusMessage *reply); + +DBusHandlerResult bus_send_error_reply( + DBusConnection *c, + DBusMessage *message, + DBusError *bus_error, + int error); + +DBusHandlerResult bus_default_message_handler( + DBusConnection *c, + DBusMessage *message, + const char *introspection, + const char *interfaces, + const BusBoundProperties *bound_properties); + +int bus_property_append_string(DBusMessageIter *i, const char *property, void *data); +int bus_property_append_strv(DBusMessageIter *i, const char *property, void *data); +int bus_property_append_bool(DBusMessageIter *i, const char *property, void *data); +int bus_property_append_tristate_false(DBusMessageIter *i, const char *property, void *data); +int bus_property_append_int32(DBusMessageIter *i, const char *property, void *data); +int bus_property_append_uint32(DBusMessageIter *i, const char *property, void *data); +int bus_property_append_uint64(DBusMessageIter *i, const char *property, void *data); +int bus_property_append_size(DBusMessageIter *i, const char *property, void *data); +int bus_property_append_ul(DBusMessageIter *i, const char *property, void *data); +int bus_property_append_long(DBusMessageIter *i, const char *property, void *data); + +#define bus_property_append_int bus_property_append_int32 +#define bus_property_append_pid bus_property_append_uint32 +#define bus_property_append_uid bus_property_append_uint32 +#define bus_property_append_gid bus_property_append_uint32 +#define bus_property_append_mode bus_property_append_uint32 +#define bus_property_append_unsigned bus_property_append_uint32 +#define bus_property_append_usec bus_property_append_uint64 + +int bus_property_set_uint64(DBusMessageIter *i, const char *property, void *data); +#define bus_property_set_usec bus_property_set_uint64 + +#define DEFINE_BUS_PROPERTY_APPEND_ENUM(function,name,type) \ + int function(DBusMessageIter *i, const char *property, void *data) { \ + const char *value; \ + type *field = data; \ + \ + assert(i); \ + assert(property); \ + \ + value = strempty(name##_to_string(*field)); \ + \ + if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &value)) \ + return -ENOMEM; \ + \ + return 0; \ + } + +#define DEFINE_BUS_PROPERTY_SET_ENUM(function,name,type) \ + int function(DBusMessageIter *i, const char *property, void *data) { \ + const char *value; \ + type f, *field = data; \ + \ + assert(i); \ + assert(property); \ + \ + dbus_message_iter_get_basic(i, &value); \ + \ + f = name##_from_string(value); \ + if (f < 0) \ + return f; \ + \ + *field = f; \ + return 0; \ + } + +const char *bus_errno_to_dbus(int error) _const_; + +DBusMessage* bus_properties_changed_new(const char *path, const char *interface, const char *properties); +DBusMessage* bus_properties_changed_one_new(const char *path, const char *interface, const char *property); + +uint32_t bus_flags_to_events(DBusWatch *bus_watch) _pure_; +unsigned bus_events_to_flags(uint32_t events) _const_; + +int bus_parse_strv(DBusMessage *m, char ***_l); +int bus_parse_strv_iter(DBusMessageIter *iter, char ***_l); +int bus_parse_strv_pairs_iter(DBusMessageIter *iter, char ***_l); + +struct unit_info { + const char *id; + const char *description; + const char *load_state; + const char *active_state; + const char *sub_state; + const char *following; + const char *unit_path; + uint32_t job_id; + const char *job_type; + const char *job_path; +}; + +int bus_parse_unit_info(DBusMessageIter *iter, struct unit_info *u); + +int bus_append_strv_iter(DBusMessageIter *iter, char **l); + +int bus_iter_get_basic_and_next(DBusMessageIter *iter, int type, void *data, bool next); + +int generic_print_property(const char *name, DBusMessageIter *iter, bool all); + +void bus_async_unregister_and_exit(DBusConnection *bus, const char *name); + +DBusHandlerResult bus_exit_idle_filter(DBusConnection *bus, DBusMessage *m, void *userdata); + +pid_t bus_get_unix_process_id(DBusConnection *connection, const char *name, DBusError *error); + +bool bus_error_is_no_service(const DBusError *error); +int bus_method_call_with_reply(DBusConnection *bus, + const char *destination, + const char *path, + const char *interface, + const char *method, + DBusMessage **return_reply, + DBusError *return_error, + int first_arg_type, ...); + +const char *bus_message_get_sender_with_fallback(DBusMessage *m); + +void bus_message_unrefp(DBusMessage **reply); + +#define _cleanup_dbus_message_unref_ __attribute__((cleanup(bus_message_unrefp))) +#define _cleanup_dbus_error_free_ __attribute__((cleanup(dbus_error_free))) diff --git a/src/shared/dbus-loop.c b/src/shared/dbus-loop.c new file mode 100644 index 0000000000..c533242220 --- /dev/null +++ b/src/shared/dbus-loop.c @@ -0,0 +1,249 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <stdbool.h> +#include <assert.h> +#include <sys/epoll.h> +#include <string.h> +#include <errno.h> +#include <sys/timerfd.h> +#include <unistd.h> + +#include "dbus-loop.h" +#include "dbus-common.h" +#include "util.h" + +/* Minimal implementation of the dbus loop which integrates all dbus + * events into a single epoll fd which we can triviall integrate with + * other loops. Note that this is not used in the main systemd daemon + * since we run a more elaborate mainloop there. */ + +typedef struct EpollData { + int fd; + void *object; + bool is_timeout:1; + bool fd_is_dupped:1; +} EpollData; + +static dbus_bool_t add_watch(DBusWatch *watch, void *data) { + _cleanup_free_ EpollData *e = NULL; + struct epoll_event ev = {}; + + assert(watch); + + e = new0(EpollData, 1); + if (!e) + return FALSE; + + e->fd = dbus_watch_get_unix_fd(watch); + e->object = watch; + e->is_timeout = false; + + ev.events = bus_flags_to_events(watch); + ev.data.ptr = e; + + if (epoll_ctl(PTR_TO_INT(data), EPOLL_CTL_ADD, e->fd, &ev) < 0) { + + if (errno != EEXIST) + return FALSE; + + /* Hmm, bloody D-Bus creates multiple watches on the + * same fd. epoll() does not like that. As a dirty + * hack we simply dup() the fd and hence get a second + * one we can safely add to the epoll(). */ + + e->fd = dup(e->fd); + if (e->fd < 0) + return FALSE; + + if (epoll_ctl(PTR_TO_INT(data), EPOLL_CTL_ADD, e->fd, &ev) < 0) { + close_nointr_nofail(e->fd); + return FALSE; + } + + e->fd_is_dupped = true; + } + + dbus_watch_set_data(watch, e, NULL); + e = NULL; /* prevent freeing */ + + return TRUE; +} + +static void remove_watch(DBusWatch *watch, void *data) { + _cleanup_free_ EpollData *e = NULL; + + assert(watch); + + e = dbus_watch_get_data(watch); + if (!e) + return; + + assert_se(epoll_ctl(PTR_TO_INT(data), EPOLL_CTL_DEL, e->fd, NULL) >= 0); + + if (e->fd_is_dupped) + close_nointr_nofail(e->fd); +} + +static void toggle_watch(DBusWatch *watch, void *data) { + EpollData *e; + struct epoll_event ev = {}; + + assert(watch); + + e = dbus_watch_get_data(watch); + if (!e) + return; + + ev.data.ptr = e; + ev.events = bus_flags_to_events(watch); + + assert_se(epoll_ctl(PTR_TO_INT(data), EPOLL_CTL_MOD, e->fd, &ev) == 0); +} + +static int timeout_arm(EpollData *e) { + struct itimerspec its = {}; + + assert(e); + assert(e->is_timeout); + + if (dbus_timeout_get_enabled(e->object)) { + timespec_store(&its.it_value, dbus_timeout_get_interval(e->object) * USEC_PER_MSEC); + its.it_interval = its.it_value; + } + + if (timerfd_settime(e->fd, 0, &its, NULL) < 0) + return -errno; + + return 0; +} + +static dbus_bool_t add_timeout(DBusTimeout *timeout, void *data) { + EpollData *e; + struct epoll_event ev = {}; + + assert(timeout); + + e = new0(EpollData, 1); + if (!e) + return FALSE; + + e->fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK|TFD_CLOEXEC); + if (e->fd < 0) + goto fail; + + e->object = timeout; + e->is_timeout = true; + + if (timeout_arm(e) < 0) + goto fail; + + ev.events = EPOLLIN; + ev.data.ptr = e; + + if (epoll_ctl(PTR_TO_INT(data), EPOLL_CTL_ADD, e->fd, &ev) < 0) + goto fail; + + dbus_timeout_set_data(timeout, e, NULL); + + return TRUE; + +fail: + if (e->fd >= 0) + close_nointr_nofail(e->fd); + + free(e); + return FALSE; +} + +static void remove_timeout(DBusTimeout *timeout, void *data) { + _cleanup_free_ EpollData *e = NULL; + + assert(timeout); + + e = dbus_timeout_get_data(timeout); + if (!e) + return; + + assert_se(epoll_ctl(PTR_TO_INT(data), EPOLL_CTL_DEL, e->fd, NULL) >= 0); + close_nointr_nofail(e->fd); +} + +static void toggle_timeout(DBusTimeout *timeout, void *data) { + EpollData *e; + int r; + + assert(timeout); + + e = dbus_timeout_get_data(timeout); + if (!e) + return; + + r = timeout_arm(e); + if (r < 0) + log_error("Failed to rearm timer: %s", strerror(-r)); +} + +int bus_loop_open(DBusConnection *c) { + int fd; + + assert(c); + + fd = epoll_create1(EPOLL_CLOEXEC); + if (fd < 0) + return -errno; + + if (!dbus_connection_set_watch_functions(c, add_watch, remove_watch, toggle_watch, INT_TO_PTR(fd), NULL) || + !dbus_connection_set_timeout_functions(c, add_timeout, remove_timeout, toggle_timeout, INT_TO_PTR(fd), NULL)) { + close_nointr_nofail(fd); + return -ENOMEM; + } + + return fd; +} + +int bus_loop_dispatch(int fd) { + int n; + struct epoll_event event = {}; + EpollData *d; + + assert(fd >= 0); + + n = epoll_wait(fd, &event, 1, 0); + if (n < 0) + return errno == EAGAIN || errno == EINTR ? 0 : -errno; + + assert_se(d = event.data.ptr); + + if (d->is_timeout) { + DBusTimeout *t = d->object; + + if (dbus_timeout_get_enabled(t)) + dbus_timeout_handle(t); + } else { + DBusWatch *w = d->object; + + if (dbus_watch_get_enabled(w)) + dbus_watch_handle(w, bus_events_to_flags(event.events)); + } + + return 0; +} diff --git a/src/shared/dbus-loop.h b/src/shared/dbus-loop.h new file mode 100644 index 0000000000..a5e768d931 --- /dev/null +++ b/src/shared/dbus-loop.h @@ -0,0 +1,27 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <dbus/dbus.h> + +int bus_loop_open(DBusConnection *c); +int bus_loop_dispatch(int fd); diff --git a/src/shared/def.h b/src/shared/def.h new file mode 100644 index 0000000000..5ba170f965 --- /dev/null +++ b/src/shared/def.h @@ -0,0 +1,35 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "util.h" + +#define DEFAULT_TIMEOUT_USEC (90*USEC_PER_SEC) +#define DEFAULT_RESTART_USEC (100*USEC_PER_MSEC) +#define DEFAULT_CONFIRM_USEC (30*USEC_PER_SEC) + +#define DEFAULT_EXIT_USEC (5*USEC_PER_MINUTE) + +#define SYSTEMD_CGROUP_CONTROLLER "name=systemd" + +#define SIGNALS_CRASH_HANDLER SIGSEGV,SIGILL,SIGFPE,SIGBUS,SIGQUIT,SIGABRT +#define SIGNALS_IGNORE SIGKILL,SIGPIPE diff --git a/src/shared/dev-setup.c b/src/shared/dev-setup.c new file mode 100644 index 0000000000..b0ac02d461 --- /dev/null +++ b/src/shared/dev-setup.c @@ -0,0 +1,78 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010-2012 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <errno.h> +#include <sys/stat.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <unistd.h> + +#include "dev-setup.h" +#include "log.h" +#include "macro.h" +#include "util.h" +#include "label.h" + +static int symlink_and_label(const char *old_path, const char *new_path) { + int r; + + assert(old_path); + assert(new_path); + + r = label_context_set(new_path, S_IFLNK); + if (r < 0) + return r; + + if (symlink(old_path, new_path) < 0) + r = -errno; + + label_context_clear(); + + return r; +} + +void dev_setup(const char *prefix) { + const char *j, *k; + + static const char symlinks[] = + "/proc/kcore\0" "/dev/core\0" + "/proc/self/fd\0" "/dev/fd\0" + "/proc/self/fd/0\0" "/dev/stdin\0" + "/proc/self/fd/1\0" "/dev/stdout\0" + "/proc/self/fd/2\0" "/dev/stderr\0"; + + NULSTR_FOREACH_PAIR(j, k, symlinks) { + + if (prefix) { + char *linkname; + + if (asprintf(&linkname, "%s/%s", prefix, k) < 0) { + log_oom(); + break; + } + + symlink_and_label(j, linkname); + free(linkname); + } else + symlink_and_label(j, k); + } +} diff --git a/src/shared/dev-setup.h b/src/shared/dev-setup.h new file mode 100644 index 0000000000..320c0b30ba --- /dev/null +++ b/src/shared/dev-setup.h @@ -0,0 +1,24 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010-2012 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +void dev_setup(const char *pathprefix); diff --git a/src/shared/efivars.c b/src/shared/efivars.c new file mode 100644 index 0000000000..8d004bad33 --- /dev/null +++ b/src/shared/efivars.c @@ -0,0 +1,503 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2013 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <unistd.h> +#include <string.h> +#include <fcntl.h> +#include <ctype.h> + +#include "util.h" +#include "utf8.h" +#include "efivars.h" + +#ifdef ENABLE_EFI + +bool is_efi_boot(void) { + return access("/sys/firmware/efi", F_OK) >= 0; +} + +static int read_flag(const char *varname) { + int r; + void *v; + size_t s; + uint8_t b; + + r = efi_get_variable(EFI_VENDOR_GLOBAL, varname, NULL, &v, &s); + if (r < 0) + return r; + + if (s != 1) { + r = -EINVAL; + goto finish; + } + + b = *(uint8_t *)v; + r = b > 0; +finish: + free(v); + return r; +} + +int is_efi_secure_boot(void) { + return read_flag("SecureBoot"); +} + +int is_efi_secure_boot_setup_mode(void) { + return read_flag("SetupMode"); +} + +int efi_get_variable( + sd_id128_t vendor, + const char *name, + uint32_t *attribute, + void **value, + size_t *size) { + + _cleanup_close_ int fd = -1; + _cleanup_free_ char *p = NULL; + uint32_t a; + ssize_t n; + struct stat st; + void *r; + + assert(name); + assert(value); + assert(size); + + if (asprintf(&p, + "/sys/firmware/efi/efivars/%s-%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", + name, SD_ID128_FORMAT_VAL(vendor)) < 0) + return -ENOMEM; + + fd = open(p, O_RDONLY|O_NOCTTY|O_CLOEXEC); + if (fd < 0) + return -errno; + + if (fstat(fd, &st) < 0) + return -errno; + if (st.st_size < 4) + return -EIO; + if (st.st_size > 4*1024*1024 + 4) + return -E2BIG; + + n = read(fd, &a, sizeof(a)); + if (n < 0) + return -errno; + if (n != sizeof(a)) + return -EIO; + + r = malloc(st.st_size - 4 + 2); + if (!r) + return -ENOMEM; + + n = read(fd, r, (size_t) st.st_size - 4); + if (n < 0) { + free(r); + return -errno; + } + if (n != (ssize_t) st.st_size - 4) { + free(r); + return -EIO; + } + + /* Always NUL terminate (2 bytes, to protect UTF-16) */ + ((char*) r)[st.st_size - 4] = 0; + ((char*) r)[st.st_size - 4 + 1] = 0; + + *value = r; + *size = (size_t) st.st_size - 4; + + if (attribute) + *attribute = a; + + return 0; +} + +int efi_get_variable_string(sd_id128_t vendor, const char *name, char **p) { + _cleanup_free_ void *s = NULL; + size_t ss; + int r; + char *x; + + r = efi_get_variable(vendor, name, NULL, &s, &ss); + if (r < 0) + return r; + + x = utf16_to_utf8(s, ss); + if (!x) + return -ENOMEM; + + *p = x; + return 0; +} + +static size_t utf16_size(const uint16_t *s) { + size_t l = 0; + + while (s[l] > 0) + l++; + + return (l+1) * sizeof(uint16_t); +} + +static void efi_guid_to_id128(const void *guid, sd_id128_t *id128) { + struct uuid { + uint32_t u1; + uint16_t u2; + uint16_t u3; + uint8_t u4[8]; + } _packed_; + const struct uuid *uuid = guid; + + id128->bytes[0] = (uuid->u1 >> 24) & 0xff; + id128->bytes[1] = (uuid->u1 >> 16) & 0xff; + id128->bytes[2] = (uuid->u1 >> 8) & 0xff; + id128->bytes[3] = (uuid->u1) & 0xff; + id128->bytes[4] = (uuid->u2 >> 8) & 0xff; + id128->bytes[5] = (uuid->u2) & 0xff; + id128->bytes[6] = (uuid->u3 >> 8) & 0xff; + id128->bytes[7] = (uuid->u3) & 0xff; + memcpy(&id128->bytes[8], uuid->u4, sizeof(uuid->u4)); +} + +int efi_get_boot_option( + uint16_t id, + char **title, + sd_id128_t *part_uuid, + char **path) { + + struct boot_option { + uint32_t attr; + uint16_t path_len; + uint16_t title[]; + } _packed_; + + struct drive_path { + uint32_t part_nr; + uint64_t part_start; + uint64_t part_size; + char signature[16]; + uint8_t mbr_type; + uint8_t signature_type; + } _packed_; + + struct device_path { + uint8_t type; + uint8_t sub_type; + uint16_t length; + union { + uint16_t path[0]; + struct drive_path drive; + }; + } _packed_; + + char boot_id[9]; + _cleanup_free_ uint8_t *buf = NULL; + size_t l; + struct boot_option *header; + size_t title_size; + char *s = NULL; + char *p = NULL; + sd_id128_t p_uuid = SD_ID128_NULL; + int err; + + snprintf(boot_id, sizeof(boot_id), "Boot%04X", id); + err = efi_get_variable(EFI_VENDOR_GLOBAL, boot_id, NULL, (void **)&buf, &l); + if (err < 0) + return err; + if (l < sizeof(struct boot_option)) + return -ENOENT; + + header = (struct boot_option *)buf; + title_size = utf16_size(header->title); + if (title_size > l - offsetof(struct boot_option, title)) + return -EINVAL; + + if (title) { + s = utf16_to_utf8(header->title, title_size); + if (!s) { + err = -ENOMEM; + goto err; + } + } + + if (header->path_len > 0) { + uint8_t *dbuf; + size_t dnext; + + dbuf = buf + offsetof(struct boot_option, title) + title_size; + dnext = 0; + while (dnext < header->path_len) { + struct device_path *dpath; + + dpath = (struct device_path *)(dbuf + dnext); + if (dpath->length < 4) + break; + + /* Type 0x7F – End of Hardware Device Path, Sub-Type 0xFF – End Entire Device Path */ + if (dpath->type == 0x7f && dpath->sub_type == 0xff) + break; + + dnext += dpath->length; + + /* Type 0x04 – Media Device Path */ + if (dpath->type != 0x04) + continue; + + /* Sub-Type 1 – Hard Drive */ + if (dpath->sub_type == 0x01) { + /* 0x02 – GUID Partition Table */ + if (dpath->drive.mbr_type != 0x02) + continue; + + /* 0x02 – GUID signature */ + if (dpath->drive.signature_type != 0x02) + continue; + + if (part_uuid) + efi_guid_to_id128(dpath->drive.signature, &p_uuid); + continue; + } + + /* Sub-Type 4 – File Path */ + if (dpath->sub_type == 0x04 && !p && path) { + p = utf16_to_utf8(dpath->path, dpath->length-4); + continue; + } + } + } + + if (title) + *title = s; + if (part_uuid) + *part_uuid = p_uuid; + if (path) + *path = p; + + return 0; +err: + free(s); + free(p); + return err; +} + +int efi_get_boot_order(uint16_t **order) { + void *buf; + size_t l; + int r; + + r = efi_get_variable(EFI_VENDOR_GLOBAL, "BootOrder", NULL, &buf, &l); + if (r < 0) + return r; + + if (l <= 0) { + free(buf); + return -ENOENT; + } + + if ((l % sizeof(uint16_t) > 0) || + (l / sizeof(uint16_t) > INT_MAX)) { + free(buf); + return -EINVAL; + } + + *order = buf; + return (int) (l / sizeof(uint16_t)); +} + +static int boot_id_hex(const char s[4]) { + int i; + int id = 0; + + for (i = 0; i < 4; i++) + if (s[i] >= '0' && s[i] <= '9') + id |= (s[i] - '0') << (3 - i) * 4; + else if (s[i] >= 'A' && s[i] <= 'F') + id |= (s[i] - 'A' + 10) << (3 - i) * 4; + else + return -1; + + return id; +} + +static int cmp_uint16(const void *_a, const void *_b) { + const uint16_t *a = _a, *b = _b; + + return (int)*a - (int)*b; +} + +int efi_get_boot_options(uint16_t **options) { + _cleanup_closedir_ DIR *dir = NULL; + struct dirent *de; + uint16_t *list = NULL; + int count = 0, r; + + assert(options); + + dir = opendir("/sys/firmware/efi/efivars/"); + if (!dir) + return -errno; + + FOREACH_DIRENT(de, dir, r = -errno; goto fail) { + int id; + uint16_t *t; + + if (strncmp(de->d_name, "Boot", 4) != 0) + continue; + + if (strlen(de->d_name) != 45) + continue; + + if (strcmp(de->d_name + 8, "-8be4df61-93ca-11d2-aa0d-00e098032b8c") != 0) + continue; + + id = boot_id_hex(de->d_name + 4); + if (id < 0) + continue; + + t = realloc(list, (count + 1) * sizeof(uint16_t)); + if (!t) { + r = -ENOMEM; + goto fail; + } + + list = t; + list[count ++] = id; + } + + qsort(list, count, sizeof(uint16_t), cmp_uint16); + + *options = list; + return count; + +fail: + free(list); + return r; +} + +static int read_usec(sd_id128_t vendor, const char *name, usec_t *u) { + _cleanup_free_ char *j = NULL; + int r; + uint64_t x; + + assert(name); + assert(u); + + r = efi_get_variable_string(EFI_VENDOR_LOADER, name, &j); + if (r < 0) + return r; + + r = safe_atou64(j, &x); + if (r < 0) + return r; + + *u = x; + return 0; +} + +static int get_boot_usec(usec_t *firmware, usec_t *loader) { + uint64_t x, y; + int r; + + assert(firmware); + assert(loader); + + r = read_usec(EFI_VENDOR_LOADER, "LoaderTimeInitUSec", &x); + if (r < 0) + return r; + + r = read_usec(EFI_VENDOR_LOADER, "LoaderTimeExecUSec", &y); + if (r < 0) + return r; + + if (y == 0 || y < x) + return -EIO; + + if (y > USEC_PER_HOUR) + return -EIO; + + *firmware = x; + *loader = y; + + return 0; +} + +int efi_get_boot_timestamps(const dual_timestamp *n, dual_timestamp *firmware, dual_timestamp *loader) { + usec_t x, y, a; + int r; + dual_timestamp _n; + + assert(firmware); + assert(loader); + + if (!n) { + dual_timestamp_get(&_n); + n = &_n; + } + + r = get_boot_usec(&x, &y); + if (r < 0) + return r; + + /* Let's convert this to timestamps where the firmware + * began/loader began working. To make this more confusing: + * since usec_t is unsigned and the kernel's monotonic clock + * begins at kernel initialization we'll actually initialize + * the monotonic timestamps here as negative of the actual + * value. */ + + firmware->monotonic = y; + loader->monotonic = y - x; + + a = n->monotonic + firmware->monotonic; + firmware->realtime = n->realtime > a ? n->realtime - a : 0; + + a = n->monotonic + loader->monotonic; + loader->realtime = n->realtime > a ? n->realtime - a : 0; + + return 0; +} + +int efi_get_loader_device_part_uuid(sd_id128_t *u) { + _cleanup_free_ char *p = NULL; + int r, parsed[16]; + unsigned i; + + assert(u); + + r = efi_get_variable_string(EFI_VENDOR_LOADER, "LoaderDevicePartUUID", &p); + if (r < 0) + return r; + + if (sscanf(p, "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", + &parsed[0], &parsed[1], &parsed[2], &parsed[3], + &parsed[4], &parsed[5], &parsed[6], &parsed[7], + &parsed[8], &parsed[9], &parsed[10], &parsed[11], + &parsed[12], &parsed[13], &parsed[14], &parsed[15]) != 16) + return -EIO; + + for (i = 0; i < ELEMENTSOF(parsed); i++) + u->bytes[i] = parsed[i]; + + return 0; +} + +#endif diff --git a/src/shared/efivars.h b/src/shared/efivars.h new file mode 100644 index 0000000000..2b88c6075c --- /dev/null +++ b/src/shared/efivars.h @@ -0,0 +1,47 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <sys/types.h> +#include <inttypes.h> +#include <stdbool.h> + +#include "sd-id128.h" +#include "time-util.h" + +#define EFI_VENDOR_LOADER SD_ID128_MAKE(4a,67,b0,82,0a,4c,41,cf,b6,c7,44,0b,29,bb,8c,4f) +#define EFI_VENDOR_GLOBAL SD_ID128_MAKE(8b,e4,df,61,93,ca,11,d2,aa,0d,00,e0,98,03,2b,8c) + +bool is_efi_boot(void); +int is_efi_secure_boot(void); +int is_efi_secure_boot_setup_mode(void); + +int efi_get_variable(sd_id128_t vendor, const char *name, uint32_t *attribute, void **value, size_t *size); +int efi_get_variable_string(sd_id128_t vendor, const char *name, char **p); + +int efi_get_boot_option(uint16_t nr, char **title, sd_id128_t *partuuid, char **path); +int efi_get_boot_order(uint16_t **order); +int efi_get_boot_options(uint16_t **options); + +int efi_get_boot_timestamps(const dual_timestamp *n, dual_timestamp *firmware, dual_timestamp *loader); + +int efi_get_loader_device_part_uuid(sd_id128_t *u); diff --git a/src/shared/env-util.c b/src/shared/env-util.c new file mode 100644 index 0000000000..6a52fb960d --- /dev/null +++ b/src/shared/env-util.c @@ -0,0 +1,415 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2012 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <limits.h> +#include <sys/param.h> +#include <unistd.h> + +#include "strv.h" +#include "utf8.h" +#include "util.h" +#include "env-util.h" + +#define VALID_CHARS_ENV_NAME \ + "0123456789" \ + "abcdefghijklmnopqrstuvwxyz" \ + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \ + "_" + +#ifndef ARG_MAX +#define ARG_MAX ((size_t) sysconf(_SC_ARG_MAX)) +#endif + +static bool env_name_is_valid_n(const char *e, size_t n) { + const char *p; + + if (!e) + return false; + + if (n <= 0) + return false; + + if (e[0] >= '0' && e[0] <= '9') + return false; + + /* POSIX says the overall size of the environment block cannot + * be > ARG_MAX, an individual assignment hence cannot be + * either. Discounting the equal sign and trailing NUL this + * hence leaves ARG_MAX-2 as longest possible variable + * name. */ + if (n > ARG_MAX - 2) + return false; + + for (p = e; p < e + n; p++) + if (!strchr(VALID_CHARS_ENV_NAME, *p)) + return false; + + return true; +} + +bool env_name_is_valid(const char *e) { + if (!e) + return false; + + return env_name_is_valid_n(e, strlen(e)); +} + +bool env_value_is_valid(const char *e) { + if (!e) + return false; + + if (!utf8_is_valid(e)) + return false; + + if (string_has_cc(e)) + return false; + + /* POSIX says the overall size of the environment block cannot + * be > ARG_MAX, an individual assignment hence cannot be + * either. Discounting the shortest possible variable name of + * length 1, the equal sign and trailing NUL this hence leaves + * ARG_MAX-3 as longest possible variable value. */ + if (strlen(e) > ARG_MAX - 3) + return false; + + return true; +} + +bool env_assignment_is_valid(const char *e) { + const char *eq; + + eq = strchr(e, '='); + if (!eq) + return false; + + if (!env_name_is_valid_n(e, eq - e)) + return false; + + if (!env_value_is_valid(eq + 1)) + return false; + + /* POSIX says the overall size of the environment block cannot + * be > ARG_MAX, hence the individual variable assignments + * cannot be either, but let's leave room for one trailing NUL + * byte. */ + if (strlen(e) > ARG_MAX - 1) + return false; + + return true; +} + +bool strv_env_is_valid(char **e) { + char **p, **q; + + STRV_FOREACH(p, e) { + size_t k; + + if (!env_assignment_is_valid(*p)) + return false; + + /* Check if there are duplicate assginments */ + k = strcspn(*p, "="); + STRV_FOREACH(q, p + 1) + if (strneq(*p, *q, k) && (*q)[k] == '=') + return false; + } + + return true; +} + +bool strv_env_name_or_assignment_is_valid(char **l) { + char **p, **q; + + STRV_FOREACH(p, l) { + if (!env_assignment_is_valid(*p) && !env_name_is_valid(*p)) + return false; + + STRV_FOREACH(q, p + 1) + if (streq(*p, *q)) + return false; + } + + return true; +} + +static int env_append(char **r, char ***k, char **a) { + assert(r); + assert(k); + + if (!a) + return 0; + + /* Add the entries of a to *k unless they already exist in *r + * in which case they are overridden instead. This assumes + * there is enough space in the r array. */ + + for (; *a; a++) { + char **j; + size_t n; + + n = strcspn(*a, "="); + + if ((*a)[n] == '=') + n++; + + for (j = r; j < *k; j++) + if (strneq(*j, *a, n)) + break; + + if (j >= *k) + (*k)++; + else + free(*j); + + *j = strdup(*a); + if (!*j) + return -ENOMEM; + } + + return 0; +} + +char **strv_env_merge(unsigned n_lists, ...) { + size_t n = 0; + char **l, **k, **r; + va_list ap; + unsigned i; + + /* Merges an arbitrary number of environment sets */ + + va_start(ap, n_lists); + for (i = 0; i < n_lists; i++) { + l = va_arg(ap, char**); + n += strv_length(l); + } + va_end(ap); + + r = new(char*, n+1); + if (!r) + return NULL; + + k = r; + + va_start(ap, n_lists); + for (i = 0; i < n_lists; i++) { + l = va_arg(ap, char**); + if (env_append(r, &k, l) < 0) + goto fail; + } + va_end(ap); + + *k = NULL; + + return r; + +fail: + va_end(ap); + strv_free(r); + + return NULL; +} + +_pure_ static bool env_match(const char *t, const char *pattern) { + assert(t); + assert(pattern); + + /* pattern a matches string a + * a matches a= + * a matches a=b + * a= matches a= + * a=b matches a=b + * a= does not match a + * a=b does not match a= + * a=b does not match a + * a=b does not match a=c */ + + if (streq(t, pattern)) + return true; + + if (!strchr(pattern, '=')) { + size_t l = strlen(pattern); + + return strneq(t, pattern, l) && t[l] == '='; + } + + return false; +} + +char **strv_env_delete(char **x, unsigned n_lists, ...) { + size_t n, i = 0; + char **k, **r; + va_list ap; + + /* Deletes every entry from x that is mentioned in the other + * string lists */ + + n = strv_length(x); + + r = new(char*, n+1); + if (!r) + return NULL; + + STRV_FOREACH(k, x) { + unsigned v; + + va_start(ap, n_lists); + for (v = 0; v < n_lists; v++) { + char **l, **j; + + l = va_arg(ap, char**); + STRV_FOREACH(j, l) + if (env_match(*k, *j)) + goto skip; + } + va_end(ap); + + r[i] = strdup(*k); + if (!r[i]) { + strv_free(r); + return NULL; + } + + i++; + continue; + + skip: + va_end(ap); + } + + r[i] = NULL; + + assert(i <= n); + + return r; +} + +char **strv_env_unset(char **l, const char *p) { + + char **f, **t; + + if (!l) + return NULL; + + assert(p); + + /* Drops every occurrence of the env var setting p in the + * string list. edits in-place. */ + + for (f = t = l; *f; f++) { + + if (env_match(*f, p)) { + free(*f); + continue; + } + + *(t++) = *f; + } + + *t = NULL; + return l; +} + +char **strv_env_set(char **x, const char *p) { + + char **k, **r; + char* m[2] = { (char*) p, NULL }; + + /* Overrides the env var setting of p, returns a new copy */ + + r = new(char*, strv_length(x)+2); + if (!r) + return NULL; + + k = r; + if (env_append(r, &k, x) < 0) + goto fail; + + if (env_append(r, &k, m) < 0) + goto fail; + + *k = NULL; + + return r; + +fail: + strv_free(r); + return NULL; +} + +char *strv_env_get_n(char **l, const char *name, size_t k) { + char **i; + + assert(name); + + if (k <= 0) + return NULL; + + STRV_FOREACH(i, l) + if (strneq(*i, name, k) && + (*i)[k] == '=') + return *i + k + 1; + + return NULL; +} + +char *strv_env_get(char **l, const char *name) { + assert(name); + + return strv_env_get_n(l, name, strlen(name)); +} + +char **strv_env_clean_log(char **e, const char *message) { + char **p, **q; + int k = 0; + + STRV_FOREACH(p, e) { + size_t n; + bool duplicate = false; + + if (!env_assignment_is_valid(*p)) { + if (message) + log_error("Ignoring invalid environment '%s': %s", *p, message); + free(*p); + continue; + } + + n = strcspn(*p, "="); + STRV_FOREACH(q, p + 1) + if (strneq(*p, *q, n) && (*q)[n] == '=') { + duplicate = true; + break; + } + + if (duplicate) { + free(*p); + continue; + } + + e[k++] = *p; + } + + e[k] = NULL; + return e; +} + +char **strv_env_clean(char **e) { + return strv_env_clean_log(e, NULL); +} diff --git a/src/shared/env-util.h b/src/shared/env-util.h new file mode 100644 index 0000000000..8d2114b64c --- /dev/null +++ b/src/shared/env-util.h @@ -0,0 +1,44 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <stdbool.h> +#include <sys/types.h> + +bool env_name_is_valid(const char *e); +bool env_value_is_valid(const char *e); +bool env_assignment_is_valid(const char *e); + +bool strv_env_is_valid(char **e); +char **strv_env_clean(char **l); +char **strv_env_clean_log(char **e, const char *message); + +bool strv_env_name_or_assignment_is_valid(char **l); + +char **strv_env_merge(unsigned n_lists, ...); +char **strv_env_delete(char **x, unsigned n_lists, ...); /* New copy */ + +char **strv_env_set(char **x, const char *p); /* New copy ... */ +char **strv_env_unset(char **l, const char *p); /* In place ... */ + +char *strv_env_get_n(char **l, const char *name, size_t k) _pure_; +char *strv_env_get(char **x, const char *n) _pure_; diff --git a/src/shared/exit-status.c b/src/shared/exit-status.c new file mode 100644 index 0000000000..45131f2b2a --- /dev/null +++ b/src/shared/exit-status.c @@ -0,0 +1,192 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <stdlib.h> +#include <sys/wait.h> + +#include "exit-status.h" +#include "set.h" +#include "macro.h" + +const char* exit_status_to_string(ExitStatus status, ExitStatusLevel level) { + + /* We cast to int here, so that -Wenum doesn't complain that + * EXIT_SUCCESS/EXIT_FAILURE aren't in the enum */ + + switch ((int) status) { + + case EXIT_SUCCESS: + return "SUCCESS"; + + case EXIT_FAILURE: + return "FAILURE"; + } + + + if (level == EXIT_STATUS_SYSTEMD || level == EXIT_STATUS_LSB) { + switch ((int) status) { + + case EXIT_CHDIR: + return "CHDIR"; + + case EXIT_NICE: + return "NICE"; + + case EXIT_FDS: + return "FDS"; + + case EXIT_EXEC: + return "EXEC"; + + case EXIT_MEMORY: + return "MEMORY"; + + case EXIT_LIMITS: + return "LIMITS"; + + case EXIT_OOM_ADJUST: + return "OOM_ADJUST"; + + case EXIT_SIGNAL_MASK: + return "SIGNAL_MASK"; + + case EXIT_STDIN: + return "STDIN"; + + case EXIT_STDOUT: + return "STDOUT"; + + case EXIT_CHROOT: + return "CHROOT"; + + case EXIT_IOPRIO: + return "IOPRIO"; + + case EXIT_TIMERSLACK: + return "TIMERSLACK"; + + case EXIT_SECUREBITS: + return "SECUREBITS"; + + case EXIT_SETSCHEDULER: + return "SETSCHEDULER"; + + case EXIT_CPUAFFINITY: + return "CPUAFFINITY"; + + case EXIT_GROUP: + return "GROUP"; + + case EXIT_USER: + return "USER"; + + case EXIT_CAPABILITIES: + return "CAPABILITIES"; + + case EXIT_CGROUP: + return "CGROUP"; + + case EXIT_SETSID: + return "SETSID"; + + case EXIT_CONFIRM: + return "CONFIRM"; + + case EXIT_STDERR: + return "STDERR"; + + case EXIT_TCPWRAP: + return "TCPWRAP"; + + case EXIT_PAM: + return "PAM"; + + case EXIT_NETWORK: + return "NETWORK"; + + case EXIT_NAMESPACE: + return "NAMESPACE"; + + case EXIT_NO_NEW_PRIVILEGES: + return "NO_NEW_PRIVILEGES"; + + case EXIT_SECCOMP: + return "SECCOMP"; + } + } + + if (level == EXIT_STATUS_LSB) { + switch ((int) status) { + + case EXIT_INVALIDARGUMENT: + return "INVALIDARGUMENT"; + + case EXIT_NOTIMPLEMENTED: + return "NOTIMPLEMENTED"; + + case EXIT_NOPERMISSION: + return "NOPERMISSION"; + + case EXIT_NOTINSTALLED: + return "NOTINSSTALLED"; + + case EXIT_NOTCONFIGURED: + return "NOTCONFIGURED"; + + case EXIT_NOTRUNNING: + return "NOTRUNNING"; + } + } + + return NULL; +} + + +bool is_clean_exit(int code, int status, ExitStatusSet *success_status) { + + if (code == CLD_EXITED) + return status == 0 || + (success_status && + set_contains(success_status->code, INT_TO_PTR(status))); + + /* If a daemon does not implement handlers for some of the + * signals that's not considered an unclean shutdown */ + if (code == CLD_KILLED) + return + status == SIGHUP || + status == SIGINT || + status == SIGTERM || + status == SIGPIPE || + (success_status && + set_contains(success_status->signal, INT_TO_PTR(status))); + + return false; +} + +bool is_clean_exit_lsb(int code, int status, ExitStatusSet *success_status) { + + if (is_clean_exit(code, status, success_status)) + return true; + + return + code == CLD_EXITED && + (status == EXIT_NOTINSTALLED || status == EXIT_NOTCONFIGURED); +} diff --git a/src/shared/exit-status.h b/src/shared/exit-status.h new file mode 100644 index 0000000000..1f035a3007 --- /dev/null +++ b/src/shared/exit-status.h @@ -0,0 +1,88 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <stdbool.h> +#include "set.h" +typedef enum ExitStatus { + /* EXIT_SUCCESS defined by libc */ + /* EXIT_FAILURE defined by libc */ + EXIT_INVALIDARGUMENT = 2, + EXIT_NOTIMPLEMENTED = 3, + EXIT_NOPERMISSION = 4, + EXIT_NOTINSTALLED = 5, + EXIT_NOTCONFIGURED = 6, + EXIT_NOTRUNNING = 7, + + /* The LSB suggests that error codes >= 200 are "reserved". We + * use them here under the assumption that they hence are + * unused by init scripts. + * + * http://refspecs.freestandards.org/LSB_3.1.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html */ + + EXIT_CHDIR = 200, + EXIT_NICE, + EXIT_FDS, + EXIT_EXEC, + EXIT_MEMORY, + EXIT_LIMITS, + EXIT_OOM_ADJUST, + EXIT_SIGNAL_MASK, + EXIT_STDIN, + EXIT_STDOUT, + EXIT_CHROOT, /* 210 */ + EXIT_IOPRIO, + EXIT_TIMERSLACK, + EXIT_SECUREBITS, + EXIT_SETSCHEDULER, + EXIT_CPUAFFINITY, + EXIT_GROUP, + EXIT_USER, + EXIT_CAPABILITIES, + EXIT_CGROUP, + EXIT_SETSID, /* 220 */ + EXIT_CONFIRM, + EXIT_STDERR, + EXIT_TCPWRAP, + EXIT_PAM, + EXIT_NETWORK, + EXIT_NAMESPACE, + EXIT_NO_NEW_PRIVILEGES, + EXIT_SECCOMP +} ExitStatus; + +typedef enum ExitStatusLevel { + EXIT_STATUS_MINIMAL, + EXIT_STATUS_SYSTEMD, + EXIT_STATUS_LSB, + EXIT_STATUS_FULL = EXIT_STATUS_LSB +} ExitStatusLevel; + +typedef struct ExitStatusSet { + Set *code; + Set *signal; +} ExitStatusSet; + +const char* exit_status_to_string(ExitStatus status, ExitStatusLevel level) _const_; + +bool is_clean_exit(int code, int status, ExitStatusSet *success_status); +bool is_clean_exit_lsb(int code, int status, ExitStatusSet *success_status); diff --git a/src/shared/fdset.c b/src/shared/fdset.c new file mode 100644 index 0000000000..fd27398ebb --- /dev/null +++ b/src/shared/fdset.c @@ -0,0 +1,236 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <errno.h> +#include <dirent.h> +#include <fcntl.h> +#include <unistd.h> + +#include "set.h" +#include "util.h" +#include "macro.h" +#include "fdset.h" +#include "sd-daemon.h" + +#define MAKE_SET(s) ((Set*) s) +#define MAKE_FDSET(s) ((FDSet*) s) + +/* Make sure we can distuingish fd 0 and NULL */ +#define FD_TO_PTR(fd) INT_TO_PTR((fd)+1) +#define PTR_TO_FD(p) (PTR_TO_INT(p)-1) + +FDSet *fdset_new(void) { + return MAKE_FDSET(set_new(trivial_hash_func, trivial_compare_func)); +} + +void fdset_free(FDSet *s) { + void *p; + + while ((p = set_steal_first(MAKE_SET(s)))) { + /* Valgrind's fd might have ended up in this set here, + * due to fdset_new_fill(). We'll ignore all failures + * here, so that the EBADFD that valgrind will return + * us on close() doesn't influence us */ + + /* When reloading duplicates of the private bus + * connection fds and suchlike are closed here, which + * has no effect at all, since they are only + * duplicates. So don't be surprised about these log + * messages. */ + + log_debug("Closing left-over fd %i", PTR_TO_FD(p)); + close_nointr(PTR_TO_FD(p)); + } + + set_free(MAKE_SET(s)); +} + +int fdset_put(FDSet *s, int fd) { + assert(s); + assert(fd >= 0); + + return set_put(MAKE_SET(s), FD_TO_PTR(fd)); +} + +int fdset_put_dup(FDSet *s, int fd) { + int copy, r; + + assert(s); + assert(fd >= 0); + + copy = fcntl(fd, F_DUPFD_CLOEXEC, 3); + if (copy < 0) + return -errno; + + r = fdset_put(s, copy); + if (r < 0) { + close_nointr_nofail(copy); + return r; + } + + return copy; +} + +bool fdset_contains(FDSet *s, int fd) { + assert(s); + assert(fd >= 0); + + return !!set_get(MAKE_SET(s), FD_TO_PTR(fd)); +} + +int fdset_remove(FDSet *s, int fd) { + assert(s); + assert(fd >= 0); + + return set_remove(MAKE_SET(s), FD_TO_PTR(fd)) ? fd : -ENOENT; +} + +int fdset_new_fill(FDSet **_s) { + DIR *d; + struct dirent *de; + int r = 0; + FDSet *s; + + assert(_s); + + /* Creates an fdset and fills in all currently open file + * descriptors. */ + + d = opendir("/proc/self/fd"); + if (!d) + return -errno; + + s = fdset_new(); + if (!s) { + r = -ENOMEM; + goto finish; + } + + while ((de = readdir(d))) { + int fd = -1; + + if (ignore_file(de->d_name)) + continue; + + r = safe_atoi(de->d_name, &fd); + if (r < 0) + goto finish; + + if (fd < 3) + continue; + + if (fd == dirfd(d)) + continue; + + r = fdset_put(s, fd); + if (r < 0) + goto finish; + } + + r = 0; + *_s = s; + s = NULL; + +finish: + closedir(d); + + /* We won't close the fds here! */ + if (s) + set_free(MAKE_SET(s)); + + return r; +} + +int fdset_cloexec(FDSet *fds, bool b) { + Iterator i; + void *p; + int r; + + assert(fds); + + SET_FOREACH(p, MAKE_SET(fds), i) + if ((r = fd_cloexec(PTR_TO_FD(p), b)) < 0) + return r; + + return 0; +} + +int fdset_new_listen_fds(FDSet **_s, bool unset) { + int n, fd, r; + FDSet *s; + + assert(_s); + + /* Creates an fdset and fills in all passed file descriptors */ + + s = fdset_new(); + if (!s) { + r = -ENOMEM; + goto fail; + } + + n = sd_listen_fds(unset); + for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; fd ++) { + r = fdset_put(s, fd); + if (r < 0) + goto fail; + } + + *_s = s; + return 0; + + +fail: + if (s) + set_free(MAKE_SET(s)); + + return r; +} + +int fdset_close_others(FDSet *fds) { + void *e; + Iterator i; + int *a; + unsigned j, m; + + j = 0, m = fdset_size(fds); + a = alloca(sizeof(int) * m); + SET_FOREACH(e, MAKE_SET(fds), i) + a[j++] = PTR_TO_FD(e); + + assert(j == m); + + return close_all_fds(a, j); +} + +unsigned fdset_size(FDSet *fds) { + return set_size(MAKE_SET(fds)); +} + +int fdset_iterate(FDSet *s, Iterator *i) { + void *p; + + p = set_iterate(MAKE_SET(s), i); + if (!p) + return -ENOENT; + + return PTR_TO_FD(p); +} diff --git a/src/shared/fdset.h b/src/shared/fdset.h new file mode 100644 index 0000000000..a7bd5e2b40 --- /dev/null +++ b/src/shared/fdset.h @@ -0,0 +1,49 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "set.h" + +typedef struct FDSet FDSet; + +FDSet* fdset_new(void); +void fdset_free(FDSet *s); + +int fdset_put(FDSet *s, int fd); +int fdset_put_dup(FDSet *s, int fd); + +bool fdset_contains(FDSet *s, int fd); +int fdset_remove(FDSet *s, int fd); + +int fdset_new_fill(FDSet **_s); +int fdset_new_listen_fds(FDSet **_s, bool unset); + +int fdset_cloexec(FDSet *fds, bool b); + +int fdset_close_others(FDSet *fds); + +unsigned fdset_size(FDSet *fds); + +int fdset_iterate(FDSet *s, Iterator *i); + +#define FDSET_FOREACH(fd, fds, i) \ + for ((i) = ITERATOR_FIRST, (fd) = fdset_iterate((fds), &(i)); (fd) >= 0; (fd) = fdset_iterate((fds), &(i))) diff --git a/src/shared/fileio-label.c b/src/shared/fileio-label.c new file mode 100644 index 0000000000..0711826e85 --- /dev/null +++ b/src/shared/fileio-label.c @@ -0,0 +1,55 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + Copyright 2010 Harald Hoyer + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "fileio-label.h" +#include "label.h" + +int write_string_file_atomic_label(const char *fn, const char *line) { + int r; + + r = label_context_set(fn, S_IFREG); + if (r < 0) + return r; + + write_string_file_atomic(fn, line); + + label_context_clear(); + + return r; +} + +int write_env_file_label(const char *fname, char **l) { + int r; + + r = label_context_set(fname, S_IFREG); + if (r < 0) + return r; + + write_env_file(fname, l); + + label_context_clear(); + + return r; +} diff --git a/src/shared/fileio-label.h b/src/shared/fileio-label.h new file mode 100644 index 0000000000..fce4fe0d73 --- /dev/null +++ b/src/shared/fileio-label.h @@ -0,0 +1,29 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + Copyright 2010 Harald Hoyer + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <stdio.h> +#include "fileio.h" + +int write_string_file_atomic_label(const char *fn, const char *line); +int write_env_file_label(const char *fname, char **l); diff --git a/src/shared/fileio.c b/src/shared/fileio.c new file mode 100644 index 0000000000..ad068bf30d --- /dev/null +++ b/src/shared/fileio.c @@ -0,0 +1,596 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <unistd.h> +#include "fileio.h" +#include "util.h" +#include "strv.h" + + +int write_string_to_file(FILE *f, const char *line) { + errno = 0; + fputs(line, f); + if (!endswith(line, "\n")) + fputc('\n', f); + + fflush(f); + + if (ferror(f)) + return errno ? -errno : -EIO; + + return 0; +} + +int write_string_file(const char *fn, const char *line) { + _cleanup_fclose_ FILE *f = NULL; + + assert(fn); + assert(line); + + f = fopen(fn, "we"); + if (!f) + return -errno; + + return write_string_to_file(f, line); +} + +int write_string_file_atomic(const char *fn, const char *line) { + _cleanup_fclose_ FILE *f = NULL; + _cleanup_free_ char *p = NULL; + int r; + + assert(fn); + assert(line); + + r = fopen_temporary(fn, &f, &p); + if (r < 0) + return r; + + fchmod_umask(fileno(f), 0644); + + errno = 0; + fputs(line, f); + if (!endswith(line, "\n")) + fputc('\n', f); + + fflush(f); + + if (ferror(f)) + r = errno ? -errno : -EIO; + else { + if (rename(p, fn) < 0) + r = -errno; + else + r = 0; + } + + if (r < 0) + unlink(p); + + return r; +} + +int read_one_line_file(const char *fn, char **line) { + _cleanup_fclose_ FILE *f = NULL; + char t[LINE_MAX], *c; + + assert(fn); + assert(line); + + f = fopen(fn, "re"); + if (!f) + return -errno; + + if (!fgets(t, sizeof(t), f)) { + + if (ferror(f)) + return errno ? -errno : -EIO; + + t[0] = 0; + } + + c = strdup(t); + if (!c) + return -ENOMEM; + truncate_nl(c); + + *line = c; + return 0; +} + +int read_full_file(const char *fn, char **contents, size_t *size) { + _cleanup_fclose_ FILE *f = NULL; + size_t n, l; + _cleanup_free_ char *buf = NULL; + struct stat st; + + assert(fn); + assert(contents); + + f = fopen(fn, "re"); + if (!f) + return -errno; + + if (fstat(fileno(f), &st) < 0) + return -errno; + + /* Safety check */ + if (st.st_size > 4*1024*1024) + return -E2BIG; + + n = st.st_size > 0 ? st.st_size : LINE_MAX; + l = 0; + + for (;;) { + char *t; + size_t k; + + t = realloc(buf, n+1); + if (!t) + return -ENOMEM; + + buf = t; + k = fread(buf + l, 1, n - l, f); + + if (k <= 0) { + if (ferror(f)) + return -errno; + + break; + } + + l += k; + n *= 2; + + /* Safety check */ + if (n > 4*1024*1024) + return -E2BIG; + } + + buf[l] = 0; + *contents = buf; + buf = NULL; + + if (size) + *size = l; + + return 0; +} + +static int parse_env_file_internal( + const char *fname, + const char *newline, + int (*push) (const char *key, char *value, void *userdata), + void *userdata) { + + _cleanup_free_ char *contents = NULL, *key = NULL; + size_t key_alloc = 0, n_key = 0, value_alloc = 0, n_value = 0, last_value_whitespace = (size_t) -1, last_key_whitespace = (size_t) -1; + char *p, *value = NULL; + int r; + + enum { + PRE_KEY, + KEY, + PRE_VALUE, + VALUE, + VALUE_ESCAPE, + SINGLE_QUOTE_VALUE, + SINGLE_QUOTE_VALUE_ESCAPE, + DOUBLE_QUOTE_VALUE, + DOUBLE_QUOTE_VALUE_ESCAPE, + COMMENT, + COMMENT_ESCAPE + } state = PRE_KEY; + + assert(fname); + assert(newline); + + r = read_full_file(fname, &contents, NULL); + if (r < 0) + return r; + + for (p = contents; *p; p++) { + char c = *p; + + switch (state) { + + case PRE_KEY: + if (strchr(COMMENTS, c)) + state = COMMENT; + else if (!strchr(WHITESPACE, c)) { + state = KEY; + last_key_whitespace = (size_t) -1; + + if (!greedy_realloc((void**) &key, &key_alloc, n_key+2)) { + r = -ENOMEM; + goto fail; + } + + key[n_key++] = c; + } + break; + + case KEY: + if (strchr(newline, c)) { + state = PRE_KEY; + n_key = 0; + } else if (c == '=') { + state = PRE_VALUE; + last_value_whitespace = (size_t) -1; + } else { + if (!strchr(WHITESPACE, c)) + last_key_whitespace = (size_t) -1; + else if (last_key_whitespace == (size_t) -1) + last_key_whitespace = n_key; + + if (!greedy_realloc((void**) &key, &key_alloc, n_key+2)) { + r = -ENOMEM; + goto fail; + } + + key[n_key++] = c; + } + + break; + + case PRE_VALUE: + if (strchr(newline, c)) { + state = PRE_KEY; + key[n_key] = 0; + + if (value) + value[n_value] = 0; + + /* strip trailing whitespace from key */ + if (last_key_whitespace != (size_t) -1) + key[last_key_whitespace] = 0; + + r = push(key, value, userdata); + if (r < 0) + goto fail; + + n_key = 0; + value = NULL; + value_alloc = n_value = 0; + + } else if (c == '\'') + state = SINGLE_QUOTE_VALUE; + else if (c == '\"') + state = DOUBLE_QUOTE_VALUE; + else if (c == '\\') + state = VALUE_ESCAPE; + else if (!strchr(WHITESPACE, c)) { + state = VALUE; + + if (!greedy_realloc((void**) &value, &value_alloc, n_value+2)) { + r = -ENOMEM; + goto fail; + } + + value[n_value++] = c; + } + + break; + + case VALUE: + if (strchr(newline, c)) { + state = PRE_KEY; + + key[n_key] = 0; + + if (value) + value[n_value] = 0; + + /* Chomp off trailing whitespace from value */ + if (last_value_whitespace != (size_t) -1) + value[last_value_whitespace] = 0; + + /* strip trailing whitespace from key */ + if (last_key_whitespace != (size_t) -1) + key[last_key_whitespace] = 0; + + r = push(key, value, userdata); + if (r < 0) + goto fail; + + n_key = 0; + value = NULL; + value_alloc = n_value = 0; + + } else if (c == '\\') { + state = VALUE_ESCAPE; + last_value_whitespace = (size_t) -1; + } else { + if (!strchr(WHITESPACE, c)) + last_value_whitespace = (size_t) -1; + else if (last_value_whitespace == (size_t) -1) + last_value_whitespace = n_value; + + if (!greedy_realloc((void**) &value, &value_alloc, n_value+2)) { + r = -ENOMEM; + goto fail; + } + + value[n_value++] = c; + } + + break; + + case VALUE_ESCAPE: + state = VALUE; + + if (!strchr(newline, c)) { + /* Escaped newlines we eat up entirely */ + if (!greedy_realloc((void**) &value, &value_alloc, n_value+2)) { + r = -ENOMEM; + goto fail; + } + + value[n_value++] = c; + } + break; + + case SINGLE_QUOTE_VALUE: + if (c == '\'') + state = PRE_VALUE; + else if (c == '\\') + state = SINGLE_QUOTE_VALUE_ESCAPE; + else { + if (!greedy_realloc((void**) &value, &value_alloc, n_value+2)) { + r = -ENOMEM; + goto fail; + } + + value[n_value++] = c; + } + + break; + + case SINGLE_QUOTE_VALUE_ESCAPE: + state = SINGLE_QUOTE_VALUE; + + if (!strchr(newline, c)) { + if (!greedy_realloc((void**) &value, &value_alloc, n_value+2)) { + r = -ENOMEM; + goto fail; + } + + value[n_value++] = c; + } + break; + + case DOUBLE_QUOTE_VALUE: + if (c == '\"') + state = PRE_VALUE; + else if (c == '\\') + state = DOUBLE_QUOTE_VALUE_ESCAPE; + else { + if (!greedy_realloc((void**) &value, &value_alloc, n_value+2)) { + r = -ENOMEM; + goto fail; + } + + value[n_value++] = c; + } + + break; + + case DOUBLE_QUOTE_VALUE_ESCAPE: + state = DOUBLE_QUOTE_VALUE; + + if (!strchr(newline, c)) { + if (!greedy_realloc((void**) &value, &value_alloc, n_value+2)) { + r = -ENOMEM; + goto fail; + } + + value[n_value++] = c; + } + break; + + case COMMENT: + if (c == '\\') + state = COMMENT_ESCAPE; + else if (strchr(newline, c)) + state = PRE_KEY; + break; + + case COMMENT_ESCAPE: + state = COMMENT; + break; + } + } + + if (state == PRE_VALUE || + state == VALUE || + state == VALUE_ESCAPE || + state == SINGLE_QUOTE_VALUE || + state == SINGLE_QUOTE_VALUE_ESCAPE || + state == DOUBLE_QUOTE_VALUE || + state == DOUBLE_QUOTE_VALUE_ESCAPE) { + + key[n_key] = 0; + + if (value) + value[n_value] = 0; + + if (state == VALUE) + if (last_value_whitespace != (size_t) -1) + value[last_value_whitespace] = 0; + + /* strip trailing whitespace from key */ + if (last_key_whitespace != (size_t) -1) + key[last_key_whitespace] = 0; + + r = push(key, value, userdata); + if (r < 0) + goto fail; + } + + return 0; + +fail: + free(value); + return r; +} + +static int parse_env_file_push(const char *key, char *value, void *userdata) { + const char *k; + va_list* ap = (va_list*) userdata; + va_list aq; + + va_copy(aq, *ap); + + while ((k = va_arg(aq, const char *))) { + char **v; + + v = va_arg(aq, char **); + + if (streq(key, k)) { + va_end(aq); + free(*v); + *v = value; + return 1; + } + } + + va_end(aq); + + free(value); + return 0; +} + +int parse_env_file( + const char *fname, + const char *newline, ...) { + + va_list ap; + int r; + + if (!newline) + newline = NEWLINE; + + va_start(ap, newline); + r = parse_env_file_internal(fname, newline, parse_env_file_push, &ap); + va_end(ap); + + return r; +} + +static int load_env_file_push(const char *key, char *value, void *userdata) { + char ***m = userdata; + char *p; + int r; + + p = strjoin(key, "=", strempty(value), NULL); + if (!p) + return -ENOMEM; + + r = strv_push(m, p); + if (r < 0) { + free(p); + return r; + } + + free(value); + return 0; +} + +int load_env_file(const char *fname, const char *newline, char ***rl) { + char **m = NULL; + int r; + + if (!newline) + newline = NEWLINE; + + r = parse_env_file_internal(fname, newline, load_env_file_push, &m); + if (r < 0) { + strv_free(m); + return r; + } + + *rl = m; + return 0; +} + +static void write_env_var(FILE *f, const char *v) { + const char *p; + + p = strchr(v, '='); + if (!p) { + /* Fallback */ + fputs(v, f); + fputc('\n', f); + return; + } + + p++; + fwrite(v, 1, p-v, f); + + if (string_has_cc(p) || chars_intersect(p, WHITESPACE "\'\"\\`$")) { + fputc('\"', f); + + for (; *p; p++) { + if (strchr("\'\"\\`$", *p)) + fputc('\\', f); + + fputc(*p, f); + } + + fputc('\"', f); + } else + fputs(p, f); + + fputc('\n', f); +} + +int write_env_file(const char *fname, char **l) { + char **i; + _cleanup_free_ char *p = NULL; + _cleanup_fclose_ FILE *f = NULL; + int r; + + r = fopen_temporary(fname, &f, &p); + if (r < 0) + return r; + + fchmod_umask(fileno(f), 0644); + + errno = 0; + STRV_FOREACH(i, l) + write_env_var(f, *i); + + fflush(f); + + if (ferror(f)) + r = errno ? -errno : -EIO; + else { + if (rename(p, fname) < 0) + r = -errno; + else + r = 0; + } + + if (r < 0) + unlink(p); + + return r; +} diff --git a/src/shared/fileio.h b/src/shared/fileio.h new file mode 100644 index 0000000000..0ca6878ea4 --- /dev/null +++ b/src/shared/fileio.h @@ -0,0 +1,37 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ +#include <stddef.h> +#include <stdio.h> + +#include "macro.h" + +int write_string_to_file(FILE *f, const char *line); +int write_string_file(const char *fn, const char *line); +int write_string_file_atomic(const char *fn, const char *line); + +int read_one_line_file(const char *fn, char **line); +int read_full_file(const char *fn, char **contents, size_t *size); + +int parse_env_file(const char *fname, const char *separator, ...) _sentinel_; +int load_env_file(const char *fname, const char *separator, char ***l); +int write_env_file(const char *fname, char **l); diff --git a/src/shared/hashmap.c b/src/shared/hashmap.c new file mode 100644 index 0000000000..9f7db34397 --- /dev/null +++ b/src/shared/hashmap.c @@ -0,0 +1,862 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <assert.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> + +#include "util.h" +#include "hashmap.h" +#include "macro.h" + +#define NBUCKETS 127 + +struct hashmap_entry { + const void *key; + void *value; + struct hashmap_entry *bucket_next, *bucket_previous; + struct hashmap_entry *iterate_next, *iterate_previous; +}; + +struct Hashmap { + hash_func_t hash_func; + compare_func_t compare_func; + + struct hashmap_entry *iterate_list_head, *iterate_list_tail; + unsigned n_entries; + + bool from_pool; +}; + +#define BY_HASH(h) ((struct hashmap_entry**) ((uint8_t*) (h) + ALIGN(sizeof(Hashmap)))) + +struct pool { + struct pool *next; + unsigned n_tiles; + unsigned n_used; +}; + +static struct pool *first_hashmap_pool = NULL; +static void *first_hashmap_tile = NULL; + +static struct pool *first_entry_pool = NULL; +static void *first_entry_tile = NULL; + +static void* allocate_tile(struct pool **first_pool, void **first_tile, size_t tile_size) { + unsigned i; + + if (*first_tile) { + void *r; + + r = *first_tile; + *first_tile = * (void**) (*first_tile); + return r; + } + + if (_unlikely_(!*first_pool) || _unlikely_((*first_pool)->n_used >= (*first_pool)->n_tiles)) { + unsigned n; + size_t size; + struct pool *p; + + n = *first_pool ? (*first_pool)->n_tiles : 0; + n = MAX(512U, n * 2); + size = PAGE_ALIGN(ALIGN(sizeof(struct pool)) + n*tile_size); + n = (size - ALIGN(sizeof(struct pool))) / tile_size; + + p = malloc(size); + if (!p) + return NULL; + + p->next = *first_pool; + p->n_tiles = n; + p->n_used = 0; + + *first_pool = p; + } + + i = (*first_pool)->n_used++; + + return ((uint8_t*) (*first_pool)) + ALIGN(sizeof(struct pool)) + i*tile_size; +} + +static void deallocate_tile(void **first_tile, void *p) { + * (void**) p = *first_tile; + *first_tile = p; +} + +#ifdef VALGRIND + +static void drop_pool(struct pool *p) { + while (p) { + struct pool *n; + n = p->next; + free(p); + p = n; + } +} + +__attribute__((destructor)) static void cleanup_pool(void) { + /* Be nice to valgrind */ + + drop_pool(first_hashmap_pool); + drop_pool(first_entry_pool); +} + +#endif + +unsigned string_hash_func(const void *p) { + unsigned hash = 5381; + const signed char *c; + + /* DJB's hash function */ + + for (c = p; *c; c++) + hash = (hash << 5) + hash + (unsigned) *c; + + return hash; +} + +int string_compare_func(const void *a, const void *b) { + return strcmp(a, b); +} + +unsigned trivial_hash_func(const void *p) { + return PTR_TO_UINT(p); +} + +int trivial_compare_func(const void *a, const void *b) { + return a < b ? -1 : (a > b ? 1 : 0); +} + +unsigned uint64_hash_func(const void *p) { + uint64_t u; + + assert_cc(sizeof(uint64_t) == 2*sizeof(unsigned)); + + u = *(const uint64_t*) p; + + return (unsigned) ((u >> 32) ^ u); +} + +int uint64_compare_func(const void *_a, const void *_b) { + uint64_t a, b; + + a = *(const uint64_t*) _a; + b = *(const uint64_t*) _b; + + return a < b ? -1 : (a > b ? 1 : 0); +} + +Hashmap *hashmap_new(hash_func_t hash_func, compare_func_t compare_func) { + bool b; + Hashmap *h; + size_t size; + + b = is_main_thread(); + + size = ALIGN(sizeof(Hashmap)) + NBUCKETS * sizeof(struct hashmap_entry*); + + if (b) { + h = allocate_tile(&first_hashmap_pool, &first_hashmap_tile, size); + if (!h) + return NULL; + + memset(h, 0, size); + } else { + h = malloc0(size); + + if (!h) + return NULL; + } + + h->hash_func = hash_func ? hash_func : trivial_hash_func; + h->compare_func = compare_func ? compare_func : trivial_compare_func; + + h->n_entries = 0; + h->iterate_list_head = h->iterate_list_tail = NULL; + + h->from_pool = b; + + return h; +} + +int hashmap_ensure_allocated(Hashmap **h, hash_func_t hash_func, compare_func_t compare_func) { + assert(h); + + if (*h) + return 0; + + if (!(*h = hashmap_new(hash_func, compare_func))) + return -ENOMEM; + + return 0; +} + +static void link_entry(Hashmap *h, struct hashmap_entry *e, unsigned hash) { + assert(h); + assert(e); + + /* Insert into hash table */ + e->bucket_next = BY_HASH(h)[hash]; + e->bucket_previous = NULL; + if (BY_HASH(h)[hash]) + BY_HASH(h)[hash]->bucket_previous = e; + BY_HASH(h)[hash] = e; + + /* Insert into iteration list */ + e->iterate_previous = h->iterate_list_tail; + e->iterate_next = NULL; + if (h->iterate_list_tail) { + assert(h->iterate_list_head); + h->iterate_list_tail->iterate_next = e; + } else { + assert(!h->iterate_list_head); + h->iterate_list_head = e; + } + h->iterate_list_tail = e; + + h->n_entries++; + assert(h->n_entries >= 1); +} + +static void unlink_entry(Hashmap *h, struct hashmap_entry *e, unsigned hash) { + assert(h); + assert(e); + + /* Remove from iteration list */ + if (e->iterate_next) + e->iterate_next->iterate_previous = e->iterate_previous; + else + h->iterate_list_tail = e->iterate_previous; + + if (e->iterate_previous) + e->iterate_previous->iterate_next = e->iterate_next; + else + h->iterate_list_head = e->iterate_next; + + /* Remove from hash table bucket list */ + if (e->bucket_next) + e->bucket_next->bucket_previous = e->bucket_previous; + + if (e->bucket_previous) + e->bucket_previous->bucket_next = e->bucket_next; + else + BY_HASH(h)[hash] = e->bucket_next; + + assert(h->n_entries >= 1); + h->n_entries--; +} + +static void remove_entry(Hashmap *h, struct hashmap_entry *e) { + unsigned hash; + + assert(h); + assert(e); + + hash = h->hash_func(e->key) % NBUCKETS; + + unlink_entry(h, e, hash); + + if (h->from_pool) + deallocate_tile(&first_entry_tile, e); + else + free(e); +} + +void hashmap_free(Hashmap*h) { + + /* Free the hashmap, but nothing in it */ + + if (!h) + return; + + hashmap_clear(h); + + if (h->from_pool) + deallocate_tile(&first_hashmap_tile, h); + else + free(h); +} + +void hashmap_free_free(Hashmap *h) { + + /* Free the hashmap and all data objects in it, but not the + * keys */ + + if (!h) + return; + + hashmap_clear_free(h); + hashmap_free(h); +} + +void hashmap_free_free_free(Hashmap *h) { + + /* Free the hashmap and all data and key objects in it */ + + if (!h) + return; + + hashmap_clear_free_free(h); + hashmap_free(h); +} + +void hashmap_clear(Hashmap *h) { + if (!h) + return; + + while (h->iterate_list_head) + remove_entry(h, h->iterate_list_head); +} + +void hashmap_clear_free(Hashmap *h) { + void *p; + + if (!h) + return; + + while ((p = hashmap_steal_first(h))) + free(p); +} + +void hashmap_clear_free_free(Hashmap *h) { + if (!h) + return; + + while (h->iterate_list_head) { + void *a, *b; + + a = h->iterate_list_head->value; + b = (void*) h->iterate_list_head->key; + remove_entry(h, h->iterate_list_head); + free(a); + free(b); + } +} + + +static struct hashmap_entry *hash_scan(Hashmap *h, unsigned hash, const void *key) { + struct hashmap_entry *e; + assert(h); + assert(hash < NBUCKETS); + + for (e = BY_HASH(h)[hash]; e; e = e->bucket_next) + if (h->compare_func(e->key, key) == 0) + return e; + + return NULL; +} + +int hashmap_put(Hashmap *h, const void *key, void *value) { + struct hashmap_entry *e; + unsigned hash; + + assert(h); + + hash = h->hash_func(key) % NBUCKETS; + + e = hash_scan(h, hash, key); + if (e) { + + if (e->value == value) + return 0; + + return -EEXIST; + } + + if (h->from_pool) + e = allocate_tile(&first_entry_pool, &first_entry_tile, sizeof(struct hashmap_entry)); + else + e = new(struct hashmap_entry, 1); + + if (!e) + return -ENOMEM; + + e->key = key; + e->value = value; + + link_entry(h, e, hash); + + return 1; +} + +int hashmap_replace(Hashmap *h, const void *key, void *value) { + struct hashmap_entry *e; + unsigned hash; + + assert(h); + + hash = h->hash_func(key) % NBUCKETS; + e = hash_scan(h, hash, key); + if (e) { + e->key = key; + e->value = value; + return 0; + } + + return hashmap_put(h, key, value); +} + +int hashmap_update(Hashmap *h, const void *key, void *value) { + struct hashmap_entry *e; + unsigned hash; + + assert(h); + + hash = h->hash_func(key) % NBUCKETS; + e = hash_scan(h, hash, key); + if (!e) + return -ENOENT; + + e->value = value; + return 0; +} + +void* hashmap_get(Hashmap *h, const void *key) { + unsigned hash; + struct hashmap_entry *e; + + if (!h) + return NULL; + + hash = h->hash_func(key) % NBUCKETS; + e = hash_scan(h, hash, key); + if (!e) + return NULL; + + return e->value; +} + +void* hashmap_get2(Hashmap *h, const void *key, void **key2) { + unsigned hash; + struct hashmap_entry *e; + + if (!h) + return NULL; + + hash = h->hash_func(key) % NBUCKETS; + e = hash_scan(h, hash, key); + if (!e) + return NULL; + + if (key2) + *key2 = (void*) e->key; + + return e->value; +} + +bool hashmap_contains(Hashmap *h, const void *key) { + unsigned hash; + + if (!h) + return false; + + hash = h->hash_func(key) % NBUCKETS; + + if (!hash_scan(h, hash, key)) + return false; + + return true; +} + +void* hashmap_remove(Hashmap *h, const void *key) { + struct hashmap_entry *e; + unsigned hash; + void *data; + + if (!h) + return NULL; + + hash = h->hash_func(key) % NBUCKETS; + + if (!(e = hash_scan(h, hash, key))) + return NULL; + + data = e->value; + remove_entry(h, e); + + return data; +} + +int hashmap_remove_and_put(Hashmap *h, const void *old_key, const void *new_key, void *value) { + struct hashmap_entry *e; + unsigned old_hash, new_hash; + + if (!h) + return -ENOENT; + + old_hash = h->hash_func(old_key) % NBUCKETS; + if (!(e = hash_scan(h, old_hash, old_key))) + return -ENOENT; + + new_hash = h->hash_func(new_key) % NBUCKETS; + if (hash_scan(h, new_hash, new_key)) + return -EEXIST; + + unlink_entry(h, e, old_hash); + + e->key = new_key; + e->value = value; + + link_entry(h, e, new_hash); + + return 0; +} + +int hashmap_remove_and_replace(Hashmap *h, const void *old_key, const void *new_key, void *value) { + struct hashmap_entry *e, *k; + unsigned old_hash, new_hash; + + if (!h) + return -ENOENT; + + old_hash = h->hash_func(old_key) % NBUCKETS; + if (!(e = hash_scan(h, old_hash, old_key))) + return -ENOENT; + + new_hash = h->hash_func(new_key) % NBUCKETS; + + if ((k = hash_scan(h, new_hash, new_key))) + if (e != k) + remove_entry(h, k); + + unlink_entry(h, e, old_hash); + + e->key = new_key; + e->value = value; + + link_entry(h, e, new_hash); + + return 0; +} + +void* hashmap_remove_value(Hashmap *h, const void *key, void *value) { + struct hashmap_entry *e; + unsigned hash; + + if (!h) + return NULL; + + hash = h->hash_func(key) % NBUCKETS; + + if (!(e = hash_scan(h, hash, key))) + return NULL; + + if (e->value != value) + return NULL; + + remove_entry(h, e); + + return value; +} + +void *hashmap_iterate(Hashmap *h, Iterator *i, const void **key) { + struct hashmap_entry *e; + + assert(i); + + if (!h) + goto at_end; + + if (*i == ITERATOR_LAST) + goto at_end; + + if (*i == ITERATOR_FIRST && !h->iterate_list_head) + goto at_end; + + e = *i == ITERATOR_FIRST ? h->iterate_list_head : (struct hashmap_entry*) *i; + + if (e->iterate_next) + *i = (Iterator) e->iterate_next; + else + *i = ITERATOR_LAST; + + if (key) + *key = e->key; + + return e->value; + +at_end: + *i = ITERATOR_LAST; + + if (key) + *key = NULL; + + return NULL; +} + +void *hashmap_iterate_backwards(Hashmap *h, Iterator *i, const void **key) { + struct hashmap_entry *e; + + assert(i); + + if (!h) + goto at_beginning; + + if (*i == ITERATOR_FIRST) + goto at_beginning; + + if (*i == ITERATOR_LAST && !h->iterate_list_tail) + goto at_beginning; + + e = *i == ITERATOR_LAST ? h->iterate_list_tail : (struct hashmap_entry*) *i; + + if (e->iterate_previous) + *i = (Iterator) e->iterate_previous; + else + *i = ITERATOR_FIRST; + + if (key) + *key = e->key; + + return e->value; + +at_beginning: + *i = ITERATOR_FIRST; + + if (key) + *key = NULL; + + return NULL; +} + +void *hashmap_iterate_skip(Hashmap *h, const void *key, Iterator *i) { + unsigned hash; + struct hashmap_entry *e; + + if (!h) + return NULL; + + hash = h->hash_func(key) % NBUCKETS; + + if (!(e = hash_scan(h, hash, key))) + return NULL; + + *i = (Iterator) e; + + return e->value; +} + +void* hashmap_first(Hashmap *h) { + + if (!h) + return NULL; + + if (!h->iterate_list_head) + return NULL; + + return h->iterate_list_head->value; +} + +void* hashmap_first_key(Hashmap *h) { + + if (!h) + return NULL; + + if (!h->iterate_list_head) + return NULL; + + return (void*) h->iterate_list_head->key; +} + +void* hashmap_last(Hashmap *h) { + + if (!h) + return NULL; + + if (!h->iterate_list_tail) + return NULL; + + return h->iterate_list_tail->value; +} + +void* hashmap_steal_first(Hashmap *h) { + void *data; + + if (!h) + return NULL; + + if (!h->iterate_list_head) + return NULL; + + data = h->iterate_list_head->value; + remove_entry(h, h->iterate_list_head); + + return data; +} + +void* hashmap_steal_first_key(Hashmap *h) { + void *key; + + if (!h) + return NULL; + + if (!h->iterate_list_head) + return NULL; + + key = (void*) h->iterate_list_head->key; + remove_entry(h, h->iterate_list_head); + + return key; +} + +unsigned hashmap_size(Hashmap *h) { + + if (!h) + return 0; + + return h->n_entries; +} + +bool hashmap_isempty(Hashmap *h) { + + if (!h) + return true; + + return h->n_entries == 0; +} + +int hashmap_merge(Hashmap *h, Hashmap *other) { + struct hashmap_entry *e; + + assert(h); + + if (!other) + return 0; + + for (e = other->iterate_list_head; e; e = e->iterate_next) { + int r; + + if ((r = hashmap_put(h, e->key, e->value)) < 0) + if (r != -EEXIST) + return r; + } + + return 0; +} + +void hashmap_move(Hashmap *h, Hashmap *other) { + struct hashmap_entry *e, *n; + + assert(h); + + /* The same as hashmap_merge(), but every new item from other + * is moved to h. This function is guaranteed to succeed. */ + + if (!other) + return; + + for (e = other->iterate_list_head; e; e = n) { + unsigned h_hash, other_hash; + + n = e->iterate_next; + + h_hash = h->hash_func(e->key) % NBUCKETS; + + if (hash_scan(h, h_hash, e->key)) + continue; + + other_hash = other->hash_func(e->key) % NBUCKETS; + + unlink_entry(other, e, other_hash); + link_entry(h, e, h_hash); + } +} + +int hashmap_move_one(Hashmap *h, Hashmap *other, const void *key) { + unsigned h_hash, other_hash; + struct hashmap_entry *e; + + if (!other) + return 0; + + assert(h); + + h_hash = h->hash_func(key) % NBUCKETS; + if (hash_scan(h, h_hash, key)) + return -EEXIST; + + other_hash = other->hash_func(key) % NBUCKETS; + if (!(e = hash_scan(other, other_hash, key))) + return -ENOENT; + + unlink_entry(other, e, other_hash); + link_entry(h, e, h_hash); + + return 0; +} + +Hashmap *hashmap_copy(Hashmap *h) { + Hashmap *copy; + + assert(h); + + if (!(copy = hashmap_new(h->hash_func, h->compare_func))) + return NULL; + + if (hashmap_merge(copy, h) < 0) { + hashmap_free(copy); + return NULL; + } + + return copy; +} + +char **hashmap_get_strv(Hashmap *h) { + char **sv; + Iterator it; + char *item; + int n; + + sv = new(char*, h->n_entries+1); + if (!sv) + return NULL; + + n = 0; + HASHMAP_FOREACH(item, h, it) + sv[n++] = item; + sv[n] = NULL; + + return sv; +} + +void *hashmap_next(Hashmap *h, const void *key) { + unsigned hash; + struct hashmap_entry *e; + + assert(h); + assert(key); + + if (!h) + return NULL; + + hash = h->hash_func(key) % NBUCKETS; + e = hash_scan(h, hash, key); + if (!e) + return NULL; + + e = e->iterate_next; + if (!e) + return NULL; + + return e->value; +} diff --git a/src/shared/hashmap.h b/src/shared/hashmap.h new file mode 100644 index 0000000000..15b7e27585 --- /dev/null +++ b/src/shared/hashmap.h @@ -0,0 +1,105 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <stdbool.h> + +#include "macro.h" + +/* Pretty straightforward hash table implementation. As a minor + * optimization a NULL hashmap object will be treated as empty hashmap + * for all read operations. That way it is not necessary to + * instantiate an object for each Hashmap use. */ + +typedef struct Hashmap Hashmap; +typedef struct _IteratorStruct _IteratorStruct; +typedef _IteratorStruct* Iterator; + +#define ITERATOR_FIRST ((Iterator) 0) +#define ITERATOR_LAST ((Iterator) -1) + +typedef unsigned (*hash_func_t)(const void *p); +typedef int (*compare_func_t)(const void *a, const void *b); + +unsigned string_hash_func(const void *p) _pure_; +int string_compare_func(const void *a, const void *b) _pure_; + +/* This will compare the passed pointers directly, and will not + * dereference them. This is hence not useful for strings or + * suchlike. */ +unsigned trivial_hash_func(const void *p) _const_; +int trivial_compare_func(const void *a, const void *b) _const_; + +unsigned uint64_hash_func(const void *p) _pure_; +int uint64_compare_func(const void *a, const void *b) _pure_; + +Hashmap *hashmap_new(hash_func_t hash_func, compare_func_t compare_func); +void hashmap_free(Hashmap *h); +void hashmap_free_free(Hashmap *h); +void hashmap_free_free_free(Hashmap *h); +Hashmap *hashmap_copy(Hashmap *h); +int hashmap_ensure_allocated(Hashmap **h, hash_func_t hash_func, compare_func_t compare_func); + +int hashmap_put(Hashmap *h, const void *key, void *value); +int hashmap_update(Hashmap *h, const void *key, void *value); +int hashmap_replace(Hashmap *h, const void *key, void *value); +void *hashmap_get(Hashmap *h, const void *key); +void *hashmap_get2(Hashmap *h, const void *key, void **rkey); +bool hashmap_contains(Hashmap *h, const void *key); +void *hashmap_remove(Hashmap *h, const void *key); +void *hashmap_remove_value(Hashmap *h, const void *key, void *value); +int hashmap_remove_and_put(Hashmap *h, const void *old_key, const void *new_key, void *value); +int hashmap_remove_and_replace(Hashmap *h, const void *old_key, const void *new_key, void *value); + +int hashmap_merge(Hashmap *h, Hashmap *other); +void hashmap_move(Hashmap *h, Hashmap *other); +int hashmap_move_one(Hashmap *h, Hashmap *other, const void *key); + +unsigned hashmap_size(Hashmap *h) _pure_; +bool hashmap_isempty(Hashmap *h) _pure_; + +void *hashmap_iterate(Hashmap *h, Iterator *i, const void **key); +void *hashmap_iterate_backwards(Hashmap *h, Iterator *i, const void **key); +void *hashmap_iterate_skip(Hashmap *h, const void *key, Iterator *i); + +void hashmap_clear(Hashmap *h); +void hashmap_clear_free(Hashmap *h); +void hashmap_clear_free_free(Hashmap *h); + +void *hashmap_steal_first(Hashmap *h); +void *hashmap_steal_first_key(Hashmap *h); +void *hashmap_first(Hashmap *h) _pure_; +void *hashmap_first_key(Hashmap *h) _pure_; +void *hashmap_last(Hashmap *h) _pure_; + +void *hashmap_next(Hashmap *h, const void *key); + +char **hashmap_get_strv(Hashmap *h); + +#define HASHMAP_FOREACH(e, h, i) \ + for ((i) = ITERATOR_FIRST, (e) = hashmap_iterate((h), &(i), NULL); (e); (e) = hashmap_iterate((h), &(i), NULL)) + +#define HASHMAP_FOREACH_KEY(e, k, h, i) \ + for ((i) = ITERATOR_FIRST, (e) = hashmap_iterate((h), &(i), (const void**) &(k)); (e); (e) = hashmap_iterate((h), &(i), (const void**) &(k))) + +#define HASHMAP_FOREACH_BACKWARDS(e, h, i) \ + for ((i) = ITERATOR_LAST, (e) = hashmap_iterate_backwards((h), &(i), NULL); (e); (e) = hashmap_iterate_backwards((h), &(i), NULL)) diff --git a/src/shared/hwclock.c b/src/shared/hwclock.c new file mode 100644 index 0000000000..cc11faa6c3 --- /dev/null +++ b/src/shared/hwclock.c @@ -0,0 +1,160 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010-2012 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <assert.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <stdlib.h> +#include <signal.h> +#include <stdio.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <sys/ioctl.h> +#include <stdarg.h> +#include <ctype.h> +#include <sys/prctl.h> +#include <sys/time.h> +#include <linux/rtc.h> + +#include "macro.h" +#include "util.h" +#include "log.h" +#include "strv.h" +#include "hwclock.h" +#include "fileio.h" + +int hwclock_get_time(struct tm *tm) { + int fd; + int err = 0; + + assert(tm); + + fd = open("/dev/rtc", O_RDONLY|O_CLOEXEC); + if (fd < 0) + return -errno; + + /* This leaves the timezone fields of struct tm + * uninitialized! */ + if (ioctl(fd, RTC_RD_TIME, tm) < 0) + err = -errno; + + /* We don't know daylight saving, so we reset this in order not + * to confused mktime(). */ + tm->tm_isdst = -1; + + close_nointr_nofail(fd); + + return err; +} + +int hwclock_set_time(const struct tm *tm) { + int fd; + int err = 0; + + assert(tm); + + fd = open("/dev/rtc", O_RDONLY|O_CLOEXEC); + if (fd < 0) + return -errno; + + if (ioctl(fd, RTC_SET_TIME, tm) < 0) + err = -errno; + + close_nointr_nofail(fd); + + return err; +} + +int hwclock_is_localtime(void) { + _cleanup_fclose_ FILE *f; + + /* + * The third line of adjtime is "UTC" or "LOCAL" or nothing. + * # /etc/adjtime + * 0.0 0 0 + * 0 + * UTC + */ + f = fopen("/etc/adjtime", "re"); + if (f) { + char line[LINE_MAX]; + bool b; + + b = fgets(line, sizeof(line), f) && + fgets(line, sizeof(line), f) && + fgets(line, sizeof(line), f); + if (!b) + return -EIO; + + truncate_nl(line); + return streq(line, "LOCAL"); + + } else if (errno != ENOENT) + return -errno; + + return 0; +} + +int hwclock_set_timezone(int *min) { + const struct timeval *tv_null = NULL; + struct timespec ts; + struct tm *tm; + int minutesdelta; + struct timezone tz; + + assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0); + assert_se(tm = localtime(&ts.tv_sec)); + minutesdelta = tm->tm_gmtoff / 60; + + tz.tz_minuteswest = -minutesdelta; + tz.tz_dsttime = 0; /* DST_NONE*/ + + /* + * If the hardware clock does not run in UTC, but in local time: + * The very first time we set the kernel's timezone, it will warp + * the clock so that it runs in UTC instead of local time. + */ + if (settimeofday(tv_null, &tz) < 0) + return -errno; + if (min) + *min = minutesdelta; + return 0; +} + +int hwclock_reset_timezone(void) { + const struct timeval *tv_null = NULL; + struct timezone tz; + + tz.tz_minuteswest = 0; + tz.tz_dsttime = 0; /* DST_NONE*/ + + /* + * The very first time we set the kernel's timezone, it will warp + * the clock. Do a dummy call here, so the time warping is sealed + * and we set only the time zone with next call. + */ + if (settimeofday(tv_null, &tz) < 0) + return -errno; + + return 0; +} diff --git a/src/shared/hwclock.h b/src/shared/hwclock.h new file mode 100644 index 0000000000..b2bdc78f0c --- /dev/null +++ b/src/shared/hwclock.h @@ -0,0 +1,31 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foohwclockhfoo +#define foohwclockhfoo + +/*** + This file is part of systemd. + + Copyright 2010-2012 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +int hwclock_is_localtime(void); +int hwclock_set_timezone(int *min); +int hwclock_reset_timezone(void); +int hwclock_get_time(struct tm *tm); +int hwclock_set_time(const struct tm *tm); + +#endif diff --git a/src/shared/install-printf.c b/src/shared/install-printf.c new file mode 100644 index 0000000000..c44459b4e0 --- /dev/null +++ b/src/shared/install-printf.c @@ -0,0 +1,132 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2013 Zbigniew JÄ™drzejewski-Szmek + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <assert.h> +#include <stdlib.h> + +#include "specifier.h" +#include "unit-name.h" +#include "util.h" +#include "install-printf.h" + +static char *specifier_prefix_and_instance(char specifier, void *data, void *userdata) { + InstallInfo *i = userdata; + assert(i); + + return unit_name_to_prefix_and_instance(i->name); +} + +static char *specifier_prefix(char specifier, void *data, void *userdata) { + InstallInfo *i = userdata; + assert(i); + + return unit_name_to_prefix(i->name); +} + +static char *specifier_instance(char specifier, void *data, void *userdata) { + InstallInfo *i = userdata; + char *instance; + int r; + + assert(i); + + r = unit_name_to_instance(i->name, &instance); + if (r < 0) + return NULL; + if (instance != NULL) + return instance; + else + return strdup(""); +} + +static char *specifier_user_name(char specifier, void *data, void *userdata) { + InstallInfo *i = userdata; + const char *username; + _cleanup_free_ char *tmp = NULL; + char *printed = NULL; + + assert(i); + + if (i->user) + username = i->user; + else + /* get USER env from env or our own uid */ + username = tmp = getusername_malloc(); + + switch (specifier) { + case 'u': + printed = strdup(username); + break; + case 'U': { + /* fish username from passwd */ + uid_t uid; + int r; + + r = get_user_creds(&username, &uid, NULL, NULL, NULL); + if (r < 0) + return NULL; + + if (asprintf(&printed, "%d", uid) < 0) + return NULL; + break; + }} + + return printed; +} + + +char *install_full_printf(InstallInfo *i, const char *format) { + + /* This is similar to unit_full_printf() but does not support + * anything path-related. + * + * %n: the full id of the unit (foo@bar.waldo) + * %N: the id of the unit without the suffix (foo@bar) + * %p: the prefix (foo) + * %i: the instance (bar) + + * %U the UID of the configured user or running user + * %u the username of the configured user or running user + * %m the machine ID of the running system + * %H the host name of the running system + * %b the boot ID of the running system + */ + + const Specifier table[] = { + { 'n', specifier_string, i->name }, + { 'N', specifier_prefix_and_instance, NULL }, + { 'p', specifier_prefix, NULL }, + { 'i', specifier_instance, NULL }, + + { 'U', specifier_user_name, NULL }, + { 'u', specifier_user_name, NULL }, + + { 'm', specifier_machine_id, NULL }, + { 'H', specifier_host_name, NULL }, + { 'b', specifier_boot_id, NULL }, + { 0, NULL, NULL } + }; + + assert(i); + assert(format); + + return specifier_printf(format, table, i); +} diff --git a/src/shared/install-printf.h b/src/shared/install-printf.h new file mode 100644 index 0000000000..46f5294d21 --- /dev/null +++ b/src/shared/install-printf.h @@ -0,0 +1,25 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2013 Zbigniew JÄ™drzejewski-Szmek + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#pragma once + +#include "install.h" +char *install_full_printf(InstallInfo *i, const char *format); diff --git a/src/shared/install.c b/src/shared/install.c new file mode 100644 index 0000000000..edf4d2a9fe --- /dev/null +++ b/src/shared/install.c @@ -0,0 +1,1945 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty <of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> +#include <string.h> +#include <fnmatch.h> + +#include "util.h" +#include "mkdir.h" +#include "hashmap.h" +#include "set.h" +#include "path-util.h" +#include "path-lookup.h" +#include "strv.h" +#include "unit-name.h" +#include "install.h" +#include "conf-parser.h" +#include "conf-files.h" +#include "specifier.h" +#include "install-printf.h" + +typedef struct { + Hashmap *will_install; + Hashmap *have_installed; +} InstallContext; + +#define _cleanup_lookup_paths_free_ \ + __attribute__((cleanup(lookup_paths_free))) +#define _cleanup_install_context_done_ \ + __attribute__((cleanup(install_context_done))) + +static int lookup_paths_init_from_scope(LookupPaths *paths, UnitFileScope scope) { + assert(paths); + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); + + zero(*paths); + + return lookup_paths_init(paths, + scope == UNIT_FILE_SYSTEM ? SYSTEMD_SYSTEM : SYSTEMD_USER, + scope == UNIT_FILE_USER, + NULL, NULL, NULL); +} + +static int get_config_path(UnitFileScope scope, bool runtime, const char *root_dir, char **ret) { + char *p = NULL; + int r; + + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); + assert(ret); + + switch (scope) { + + case UNIT_FILE_SYSTEM: + + if (root_dir && runtime) + asprintf(&p, "%s/run/systemd/system", root_dir); + else if (runtime) + p = strdup("/run/systemd/system"); + else if (root_dir) + asprintf(&p, "%s/%s", root_dir, SYSTEM_CONFIG_UNIT_PATH); + else + p = strdup(SYSTEM_CONFIG_UNIT_PATH); + + break; + + case UNIT_FILE_GLOBAL: + + if (root_dir) + return -EINVAL; + + if (runtime) + p = strdup("/run/systemd/user"); + else + p = strdup(USER_CONFIG_UNIT_PATH); + break; + + case UNIT_FILE_USER: + + if (root_dir || runtime) + return -EINVAL; + + r = user_config_home(&p); + if (r <= 0) + return r < 0 ? r : -ENOENT; + + break; + + default: + assert_not_reached("Bad scope"); + } + + if (!p) + return -ENOMEM; + + *ret = p; + return 0; +} + +static int add_file_change( + UnitFileChange **changes, + unsigned *n_changes, + UnitFileChangeType type, + const char *path, + const char *source) { + + UnitFileChange *c; + unsigned i; + + assert(path); + assert(!changes == !n_changes); + + if (!changes) + return 0; + + c = realloc(*changes, (*n_changes + 1) * sizeof(UnitFileChange)); + if (!c) + return -ENOMEM; + + *changes = c; + i = *n_changes; + + c[i].type = type; + c[i].path = strdup(path); + if (!c[i].path) + return -ENOMEM; + + if (source) { + c[i].source = strdup(source); + if (!c[i].source) { + free(c[i].path); + return -ENOMEM; + } + } else + c[i].source = NULL; + + *n_changes = i+1; + return 0; +} + +static int mark_symlink_for_removal( + Set **remove_symlinks_to, + const char *p) { + + char *n; + int r; + + assert(p); + + r = set_ensure_allocated(remove_symlinks_to, string_hash_func, string_compare_func); + if (r < 0) + return r; + + n = strdup(p); + if (!n) + return -ENOMEM; + + path_kill_slashes(n); + + r = set_consume(*remove_symlinks_to, n); + if (r < 0) + return r == -EEXIST ? 0 : r; + + return 0; +} + +static int remove_marked_symlinks_fd( + Set *remove_symlinks_to, + int fd, + const char *path, + const char *config_path, + bool *deleted, + UnitFileChange **changes, + unsigned *n_changes, + char** files) { + + int r = 0; + _cleanup_closedir_ DIR *d = NULL; + + assert(remove_symlinks_to); + assert(fd >= 0); + assert(path); + assert(config_path); + assert(deleted); + + d = fdopendir(fd); + if (!d) { + close_nointr_nofail(fd); + return -errno; + } + + rewinddir(d); + + for (;;) { + struct dirent *de; + union dirent_storage buf; + int k; + + k = readdir_r(d, &buf.de, &de); + if (k != 0) { + r = -errno; + break; + } + + if (!de) + break; + + if (ignore_file(de->d_name)) + continue; + + dirent_ensure_type(d, de); + + if (de->d_type == DT_DIR) { + int nfd, q; + _cleanup_free_ char *p = NULL; + + nfd = openat(fd, de->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); + if (nfd < 0) { + if (errno == ENOENT) + continue; + + if (r == 0) + r = -errno; + continue; + } + + p = path_make_absolute(de->d_name, path); + if (!p) { + close_nointr_nofail(nfd); + return -ENOMEM; + } + + /* This will close nfd, regardless whether it succeeds or not */ + q = remove_marked_symlinks_fd(remove_symlinks_to, nfd, p, config_path, deleted, changes, n_changes, files); + + if (r == 0) + r = q; + + } else if (de->d_type == DT_LNK) { + _cleanup_free_ char *p = NULL, *dest = NULL; + int q; + bool found; + + p = path_make_absolute(de->d_name, path); + if (!p) + return -ENOMEM; + + q = readlink_and_canonicalize(p, &dest); + if (q < 0) { + if (q == -ENOENT) + continue; + + if (r == 0) + r = q; + continue; + } + + found = + set_get(remove_symlinks_to, dest) || + set_get(remove_symlinks_to, path_get_file_name(dest)); + + if (unit_name_is_instance(p)) + found = found && strv_contains(files, path_get_file_name(p)); + + if (found) { + + if (unlink(p) < 0 && errno != ENOENT) { + + if (r == 0) + r = -errno; + } else { + rmdir_parents(p, config_path); + path_kill_slashes(p); + + add_file_change(changes, n_changes, UNIT_FILE_UNLINK, p, NULL); + + if (!set_get(remove_symlinks_to, p)) { + + q = mark_symlink_for_removal(&remove_symlinks_to, p); + if (q < 0) { + if (r == 0) + r = q; + } else + *deleted = true; + } + } + } + } + } + + return r; +} + +static int remove_marked_symlinks( + Set *remove_symlinks_to, + const char *config_path, + UnitFileChange **changes, + unsigned *n_changes, + char** files) { + + int fd, r = 0; + bool deleted; + + assert(config_path); + + if (set_size(remove_symlinks_to) <= 0) + return 0; + + fd = open(config_path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); + if (fd < 0) + return -errno; + + do { + int q, cfd; + deleted = false; + + cfd = dup(fd); + if (cfd < 0) { + r = -errno; + break; + } + + /* This takes possession of cfd and closes it */ + q = remove_marked_symlinks_fd(remove_symlinks_to, cfd, config_path, config_path, &deleted, changes, n_changes, files); + if (r == 0) + r = q; + } while (deleted); + + close_nointr_nofail(fd); + + return r; +} + +static int find_symlinks_fd( + const char *name, + int fd, + const char *path, + const char *config_path, + bool *same_name_link) { + + int r = 0; + _cleanup_closedir_ DIR *d = NULL; + + assert(name); + assert(fd >= 0); + assert(path); + assert(config_path); + assert(same_name_link); + + d = fdopendir(fd); + if (!d) { + close_nointr_nofail(fd); + return -errno; + } + + for (;;) { + int k; + struct dirent *de; + union dirent_storage buf; + + k = readdir_r(d, &buf.de, &de); + if (k != 0) + return -errno; + + if (!de) + return r; + + if (ignore_file(de->d_name)) + continue; + + dirent_ensure_type(d, de); + + if (de->d_type == DT_DIR) { + int nfd, q; + _cleanup_free_ char *p = NULL; + + nfd = openat(fd, de->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); + if (nfd < 0) { + if (errno == ENOENT) + continue; + + if (r == 0) + r = -errno; + continue; + } + + p = path_make_absolute(de->d_name, path); + if (!p) { + close_nointr_nofail(nfd); + return -ENOMEM; + } + + /* This will close nfd, regardless whether it succeeds or not */ + q = find_symlinks_fd(name, nfd, p, config_path, same_name_link); + + if (q > 0) + return 1; + + if (r == 0) + r = q; + + } else if (de->d_type == DT_LNK) { + _cleanup_free_ char *p = NULL, *dest = NULL; + bool found_path, found_dest, b = false; + int q; + + /* Acquire symlink name */ + p = path_make_absolute(de->d_name, path); + if (!p) + return -ENOMEM; + + /* Acquire symlink destination */ + q = readlink_and_canonicalize(p, &dest); + if (q < 0) { + if (q == -ENOENT) + continue; + + if (r == 0) + r = q; + continue; + } + + /* Check if the symlink itself matches what we + * are looking for */ + if (path_is_absolute(name)) + found_path = path_equal(p, name); + else + found_path = streq(de->d_name, name); + + /* Check if what the symlink points to + * matches what we are looking for */ + if (path_is_absolute(name)) + found_dest = path_equal(dest, name); + else + found_dest = streq(path_get_file_name(dest), name); + + if (found_path && found_dest) { + _cleanup_free_ char *t = NULL; + + /* Filter out same name links in the main + * config path */ + t = path_make_absolute(name, config_path); + if (!t) + return -ENOMEM; + + b = path_equal(t, p); + } + + if (b) + *same_name_link = true; + else if (found_path || found_dest) + return 1; + } + } + + return r; +} + +static int find_symlinks( + const char *name, + const char *config_path, + bool *same_name_link) { + + int fd; + + assert(name); + assert(config_path); + assert(same_name_link); + + fd = open(config_path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); + if (fd < 0) { + if (errno == ENOENT) + return 0; + return -errno; + } + + /* This takes possession of fd and closes it */ + return find_symlinks_fd(name, fd, config_path, config_path, same_name_link); +} + +static int find_symlinks_in_scope( + UnitFileScope scope, + const char *root_dir, + const char *name, + UnitFileState *state) { + + int r; + _cleanup_free_ char *path = NULL; + bool same_name_link_runtime = false, same_name_link = false; + + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); + assert(name); + + if (scope == UNIT_FILE_SYSTEM || scope == UNIT_FILE_GLOBAL) { + + /* First look in runtime config path */ + r = get_config_path(scope, true, root_dir, &path); + if (r < 0) + return r; + + r = find_symlinks(name, path, &same_name_link_runtime); + if (r < 0) + return r; + else if (r > 0) { + *state = UNIT_FILE_ENABLED_RUNTIME; + return r; + } + } + + /* Then look in the normal config path */ + r = get_config_path(scope, false, root_dir, &path); + if (r < 0) + return r; + + r = find_symlinks(name, path, &same_name_link); + if (r < 0) + return r; + else if (r > 0) { + *state = UNIT_FILE_ENABLED; + return r; + } + + /* Hmm, we didn't find it, but maybe we found the same name + * link? */ + if (same_name_link_runtime) { + *state = UNIT_FILE_LINKED_RUNTIME; + return 1; + } else if (same_name_link) { + *state = UNIT_FILE_LINKED; + return 1; + } + + return 0; +} + +int unit_file_mask( + UnitFileScope scope, + bool runtime, + const char *root_dir, + char *files[], + bool force, + UnitFileChange **changes, + unsigned *n_changes) { + + char **i; + _cleanup_free_ char *prefix; + int r; + + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); + + r = get_config_path(scope, runtime, root_dir, &prefix); + if (r < 0) + return r; + + STRV_FOREACH(i, files) { + _cleanup_free_ char *path = NULL; + + if (!unit_name_is_valid(*i, true)) { + if (r == 0) + r = -EINVAL; + continue; + } + + path = path_make_absolute(*i, prefix); + if (!path) { + r = -ENOMEM; + break; + } + + if (symlink("/dev/null", path) >= 0) { + add_file_change(changes, n_changes, UNIT_FILE_SYMLINK, path, "/dev/null"); + + continue; + } + + if (errno == EEXIST) { + + if (null_or_empty_path(path) > 0) + continue; + + if (force) { + unlink(path); + + if (symlink("/dev/null", path) >= 0) { + + add_file_change(changes, n_changes, UNIT_FILE_UNLINK, path, NULL); + add_file_change(changes, n_changes, UNIT_FILE_SYMLINK, path, "/dev/null"); + + continue; + } + } + + if (r == 0) + r = -EEXIST; + } else { + if (r == 0) + r = -errno; + } + } + + return r; +} + +int unit_file_unmask( + UnitFileScope scope, + bool runtime, + const char *root_dir, + char *files[], + UnitFileChange **changes, + unsigned *n_changes) { + + char **i, *config_path = NULL; + int r, q; + Set *remove_symlinks_to = NULL; + + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); + + r = get_config_path(scope, runtime, root_dir, &config_path); + if (r < 0) + goto finish; + + STRV_FOREACH(i, files) { + char *path; + + if (!unit_name_is_valid(*i, true)) { + if (r == 0) + r = -EINVAL; + continue; + } + + path = path_make_absolute(*i, config_path); + if (!path) { + r = -ENOMEM; + break; + } + + q = null_or_empty_path(path); + if (q > 0) { + if (unlink(path) >= 0) { + mark_symlink_for_removal(&remove_symlinks_to, path); + add_file_change(changes, n_changes, UNIT_FILE_UNLINK, path, NULL); + + free(path); + continue; + } + + q = -errno; + } + + if (q != -ENOENT && r == 0) + r = q; + + free(path); + } + + +finish: + q = remove_marked_symlinks(remove_symlinks_to, config_path, changes, n_changes, files); + if (r == 0) + r = q; + + set_free_free(remove_symlinks_to); + free(config_path); + + return r; +} + +int unit_file_link( + UnitFileScope scope, + bool runtime, + const char *root_dir, + char *files[], + bool force, + UnitFileChange **changes, + unsigned *n_changes) { + + _cleanup_lookup_paths_free_ LookupPaths paths = {}; + char **i; + _cleanup_free_ char *config_path = NULL; + int r, q; + + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); + + r = lookup_paths_init_from_scope(&paths, scope); + if (r < 0) + return r; + + r = get_config_path(scope, runtime, root_dir, &config_path); + if (r < 0) + return r; + + STRV_FOREACH(i, files) { + _cleanup_free_ char *path = NULL; + char *fn; + struct stat st; + + fn = path_get_file_name(*i); + + if (!path_is_absolute(*i) || + !unit_name_is_valid(fn, true)) { + if (r == 0) + r = -EINVAL; + continue; + } + + if (lstat(*i, &st) < 0) { + if (r == 0) + r = -errno; + continue; + } + + if (!S_ISREG(st.st_mode)) { + r = -ENOENT; + continue; + } + + q = in_search_path(*i, paths.unit_path); + if (q < 0) + return q; + + if (q > 0) + continue; + + path = path_make_absolute(fn, config_path); + if (!path) + return -ENOMEM; + + if (symlink(*i, path) >= 0) { + add_file_change(changes, n_changes, UNIT_FILE_SYMLINK, path, *i); + continue; + } + + if (errno == EEXIST) { + _cleanup_free_ char *dest = NULL; + + q = readlink_and_make_absolute(path, &dest); + + if (q < 0 && errno != ENOENT) { + if (r == 0) + r = q; + continue; + } + + if (q >= 0 && path_equal(dest, *i)) + continue; + + if (force) { + unlink(path); + + if (symlink(*i, path) >= 0) { + + add_file_change(changes, n_changes, UNIT_FILE_UNLINK, path, NULL); + add_file_change(changes, n_changes, UNIT_FILE_SYMLINK, path, *i); + + continue; + } + } + + if (r == 0) + r = -EEXIST; + } else { + if (r == 0) + r = -errno; + } + } + + return r; +} + +void unit_file_list_free(Hashmap *h) { + UnitFileList *i; + + while ((i = hashmap_steal_first(h))) { + free(i->path); + free(i); + } + + hashmap_free(h); +} + +void unit_file_changes_free(UnitFileChange *changes, unsigned n_changes) { + unsigned i; + + assert(changes || n_changes == 0); + + if (!changes) + return; + + for (i = 0; i < n_changes; i++) { + free(changes[i].path); + free(changes[i].source); + } + + free(changes); +} + +static void install_info_free(InstallInfo *i) { + assert(i); + + free(i->name); + free(i->path); + strv_free(i->aliases); + strv_free(i->wanted_by); + strv_free(i->required_by); + free(i); +} + +static void install_info_hashmap_free(Hashmap *m) { + InstallInfo *i; + + if (!m) + return; + + while ((i = hashmap_steal_first(m))) + install_info_free(i); + + hashmap_free(m); +} + +static void install_context_done(InstallContext *c) { + assert(c); + + install_info_hashmap_free(c->will_install); + install_info_hashmap_free(c->have_installed); + + c->will_install = c->have_installed = NULL; +} + +static int install_info_add( + InstallContext *c, + const char *name, + const char *path) { + InstallInfo *i = NULL; + int r; + + assert(c); + assert(name || path); + + if (!name) + name = path_get_file_name(path); + + if (!unit_name_is_valid(name, true)) + return -EINVAL; + + if (hashmap_get(c->have_installed, name) || + hashmap_get(c->will_install, name)) + return 0; + + r = hashmap_ensure_allocated(&c->will_install, string_hash_func, string_compare_func); + if (r < 0) + return r; + + i = new0(InstallInfo, 1); + if (!i) + return -ENOMEM; + + i->name = strdup(name); + if (!i->name) { + r = -ENOMEM; + goto fail; + } + + if (path) { + i->path = strdup(path); + if (!i->path) { + r = -ENOMEM; + goto fail; + } + } + + r = hashmap_put(c->will_install, i->name, i); + if (r < 0) + goto fail; + + return 0; + +fail: + if (i) + install_info_free(i); + + return r; +} + +static int install_info_add_auto( + InstallContext *c, + const char *name_or_path) { + + assert(c); + assert(name_or_path); + + if (path_is_absolute(name_or_path)) + return install_info_add(c, NULL, name_or_path); + else + return install_info_add(c, name_or_path, NULL); +} + +static int config_parse_also(const char *unit, + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + char *w; + size_t l; + char *state; + InstallContext *c = data; + + assert(filename); + assert(lvalue); + assert(rvalue); + + FOREACH_WORD_QUOTED(w, l, rvalue, state) { + _cleanup_free_ char *n; + int r; + + n = strndup(w, l); + if (!n) + return -ENOMEM; + + r = install_info_add(c, n, NULL); + if (r < 0) + return r; + } + + return 0; +} + +static int config_parse_user(const char *unit, + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + InstallInfo *i = data; + char* printed; + + assert(filename); + assert(lvalue); + assert(rvalue); + + printed = install_full_printf(i, rvalue); + if (!printed) + return -ENOMEM; + + free(i->user); + i->user = printed; + + return 0; +} + +static int unit_file_load( + InstallContext *c, + InstallInfo *info, + const char *path, + bool allow_symlink) { + + const ConfigTableItem items[] = { + { "Install", "Alias", config_parse_strv, 0, &info->aliases }, + { "Install", "WantedBy", config_parse_strv, 0, &info->wanted_by }, + { "Install", "RequiredBy", config_parse_strv, 0, &info->required_by }, + { "Install", "Also", config_parse_also, 0, c }, + { "Exec", "User", config_parse_user, 0, info }, + { NULL, NULL, NULL, 0, NULL } + }; + + int fd; + _cleanup_fclose_ FILE *f = NULL; + int r; + + assert(c); + assert(info); + assert(path); + + fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY|(allow_symlink ? 0 : O_NOFOLLOW)); + if (fd < 0) + return -errno; + + f = fdopen(fd, "re"); + if (!f) { + close_nointr_nofail(fd); + return -ENOMEM; + } + + r = config_parse(NULL, path, f, NULL, + config_item_table_lookup, (void*) items, true, true, info); + if (r < 0) + return r; + + return + strv_length(info->aliases) + + strv_length(info->wanted_by) + + strv_length(info->required_by); +} + +static int unit_file_search( + InstallContext *c, + InstallInfo *info, + LookupPaths *paths, + const char *root_dir, + bool allow_symlink) { + + char **p; + int r; + + assert(c); + assert(info); + assert(paths); + + if (info->path) + return unit_file_load(c, info, info->path, allow_symlink); + + assert(info->name); + + STRV_FOREACH(p, paths->unit_path) { + char *path = NULL; + + if (isempty(root_dir)) + asprintf(&path, "%s/%s", *p, info->name); + else + asprintf(&path, "%s/%s/%s", root_dir, *p, info->name); + + if (!path) + return -ENOMEM; + + r = unit_file_load(c, info, path, allow_symlink); + + if (r >= 0) + info->path = path; + else { + if (r == -ENOENT && unit_name_is_instance(info->name)) { + /* unit file doesn't exist, however instance enablement was request */ + /* we will check if it is possible to load template unit file */ + char *template = NULL, + *template_path = NULL, + *template_dir = NULL; + + template = unit_name_template(info->name); + if (!template) { + free(path); + return -ENOMEM; + } + + /* we will reuse path variable since we don't need it anymore */ + template_dir = path; + *(strrchr(path, '/') + 1) = '\0'; + + template_path = strjoin(template_dir, template, NULL); + if (!template_path) { + free(path); + free(template); + return -ENOMEM; + } + + /* let's try to load template unit */ + r = unit_file_load(c, info, template_path, allow_symlink); + if (r >= 0) { + info->path = strdup(template_path); + if (!info->path) { + free(path); + free(template); + free(template_path); + return -ENOMEM; + } + } + + free(template); + free(template_path); + } + free(path); + } + + if (r != -ENOENT && r != -ELOOP) + return r; + } + + return -ENOENT; +} + +static int unit_file_can_install( + LookupPaths *paths, + const char *root_dir, + const char *name, + bool allow_symlink) { + + _cleanup_install_context_done_ InstallContext c = {}; + InstallInfo *i; + int r; + + assert(paths); + assert(name); + + r = install_info_add_auto(&c, name); + if (r < 0) + return r; + + assert_se(i = hashmap_first(c.will_install)); + + r = unit_file_search(&c, i, paths, root_dir, allow_symlink); + + if (r >= 0) + r = + strv_length(i->aliases) + + strv_length(i->wanted_by) + + strv_length(i->required_by); + + return r; +} + +static int create_symlink( + const char *old_path, + const char *new_path, + bool force, + UnitFileChange **changes, + unsigned *n_changes) { + + _cleanup_free_ char *dest = NULL; + int r; + + assert(old_path); + assert(new_path); + + mkdir_parents_label(new_path, 0755); + + if (symlink(old_path, new_path) >= 0) { + add_file_change(changes, n_changes, UNIT_FILE_SYMLINK, new_path, old_path); + return 0; + } + + if (errno != EEXIST) + return -errno; + + r = readlink_and_make_absolute(new_path, &dest); + if (r < 0) + return r; + + if (path_equal(dest, old_path)) + return 0; + + if (!force) + return -EEXIST; + + unlink(new_path); + + if (symlink(old_path, new_path) >= 0) { + add_file_change(changes, n_changes, UNIT_FILE_UNLINK, new_path, NULL); + add_file_change(changes, n_changes, UNIT_FILE_SYMLINK, new_path, old_path); + return 0; + } + + return -errno; +} + +static int install_info_symlink_alias( + InstallInfo *i, + const char *config_path, + bool force, + UnitFileChange **changes, + unsigned *n_changes) { + + char **s; + int r = 0, q; + + assert(i); + assert(config_path); + + STRV_FOREACH(s, i->aliases) { + _cleanup_free_ char *alias_path = NULL, *dst = NULL; + + dst = install_full_printf(i, *s); + if (!dst) + return -ENOMEM; + + alias_path = path_make_absolute(dst, config_path); + if (!alias_path) + return -ENOMEM; + + q = create_symlink(i->path, alias_path, force, changes, n_changes); + if (r == 0) + r = q; + } + + return r; +} + +static int install_info_symlink_wants( + InstallInfo *i, + const char *config_path, + bool force, + UnitFileChange **changes, + unsigned *n_changes) { + + char **s; + int r = 0, q; + + assert(i); + assert(config_path); + + STRV_FOREACH(s, i->wanted_by) { + _cleanup_free_ char *path = NULL, *dst = NULL; + + dst = install_full_printf(i, *s); + if (!dst) + return -ENOMEM; + + if (!unit_name_is_valid(dst, true)) { + r = -EINVAL; + continue; + } + + if (asprintf(&path, "%s/%s.wants/%s", config_path, dst, i->name) < 0) + return -ENOMEM; + + q = create_symlink(i->path, path, force, changes, n_changes); + + if (r == 0) + r = q; + } + + return r; +} + +static int install_info_symlink_requires( + InstallInfo *i, + const char *config_path, + bool force, + UnitFileChange **changes, + unsigned *n_changes) { + + char **s; + int r = 0, q; + + assert(i); + assert(config_path); + + STRV_FOREACH(s, i->required_by) { + _cleanup_free_ char *path = NULL, *dst = NULL; + + dst = install_full_printf(i, *s); + if (!dst) + return -ENOMEM; + + if (!unit_name_is_valid(dst, true)) { + r = -EINVAL; + continue; + } + + if (asprintf(&path, "%s/%s.requires/%s", config_path, dst, i->name) < 0) + return -ENOMEM; + + q = create_symlink(i->path, path, force, changes, n_changes); + + if (r == 0) + r = q; + } + + return r; +} + +static int install_info_symlink_link( + InstallInfo *i, + LookupPaths *paths, + const char *config_path, + bool force, + UnitFileChange **changes, + unsigned *n_changes) { + + int r; + _cleanup_free_ char *path = NULL; + + assert(i); + assert(paths); + assert(config_path); + assert(i->path); + + r = in_search_path(i->path, paths->unit_path); + if (r != 0) + return r; + + if (asprintf(&path, "%s/%s", config_path, i->name) < 0) + return -ENOMEM; + + r = create_symlink(i->path, path, force, changes, n_changes); + return r; +} + +static int install_info_apply( + InstallInfo *i, + LookupPaths *paths, + const char *config_path, + bool force, + UnitFileChange **changes, + unsigned *n_changes) { + + int r, q; + + assert(i); + assert(paths); + assert(config_path); + + r = install_info_symlink_alias(i, config_path, force, changes, n_changes); + + q = install_info_symlink_wants(i, config_path, force, changes, n_changes); + if (r == 0) + r = q; + + q = install_info_symlink_requires(i, config_path, force, changes, n_changes); + if (r == 0) + r = q; + + q = install_info_symlink_link(i, paths, config_path, force, changes, n_changes); + if (r == 0) + r = q; + + return r; +} + +static int install_context_apply( + InstallContext *c, + LookupPaths *paths, + const char *config_path, + const char *root_dir, + bool force, + UnitFileChange **changes, + unsigned *n_changes) { + + InstallInfo *i; + int r = 0, q; + + assert(c); + assert(paths); + assert(config_path); + + while ((i = hashmap_first(c->will_install))) { + + q = hashmap_ensure_allocated(&c->have_installed, string_hash_func, string_compare_func); + if (q < 0) + return q; + + assert_se(hashmap_move_one(c->have_installed, c->will_install, i->name) == 0); + + q = unit_file_search(c, i, paths, root_dir, false); + if (q < 0) { + if (r >= 0) + r = q; + + return r; + } else if (r >= 0) + r += q; + + q = install_info_apply(i, paths, config_path, force, changes, n_changes); + if (r >= 0 && q < 0) + r = q; + } + + return r; +} + +static int install_context_mark_for_removal( + InstallContext *c, + LookupPaths *paths, + Set **remove_symlinks_to, + const char *config_path, + const char *root_dir) { + + InstallInfo *i; + int r = 0, q; + + assert(c); + assert(paths); + assert(config_path); + + /* Marks all items for removal */ + + while ((i = hashmap_first(c->will_install))) { + + q = hashmap_ensure_allocated(&c->have_installed, string_hash_func, string_compare_func); + if (q < 0) + return q; + + assert_se(hashmap_move_one(c->have_installed, c->will_install, i->name) == 0); + + q = unit_file_search(c, i, paths, root_dir, false); + if (q < 0) { + if (r >= 0) + r = q; + + return r; + } else if (r >= 0) + r += q; + + if (unit_name_is_instance(i->name)) { + char *unit_file = NULL; + + unit_file = path_get_file_name(i->path); + + if (unit_name_is_instance(unit_file)) + /* unit file named as instance exists, thus all symlinks pointing to it, will be removed */ + q = mark_symlink_for_removal(remove_symlinks_to, i->name); + else + /* does not exist, thus we will mark for removal symlinks to template unit file */ + q = mark_symlink_for_removal(remove_symlinks_to, unit_file); + } else + q = mark_symlink_for_removal(remove_symlinks_to, i->name); + + if (r >= 0 && q < 0) + r = q; + } + + return r; +} + +int unit_file_enable( + UnitFileScope scope, + bool runtime, + const char *root_dir, + char *files[], + bool force, + UnitFileChange **changes, + unsigned *n_changes) { + + _cleanup_lookup_paths_free_ LookupPaths paths = {}; + _cleanup_install_context_done_ InstallContext c = {}; + char **i; + _cleanup_free_ char *config_path = NULL; + int r; + + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); + + r = lookup_paths_init_from_scope(&paths, scope); + if (r < 0) + return r; + + r = get_config_path(scope, runtime, root_dir, &config_path); + if (r < 0) + return r; + + STRV_FOREACH(i, files) { + r = install_info_add_auto(&c, *i); + if (r < 0) + return r; + } + + /* This will return the number of symlink rules that were + supposed to be created, not the ones actually created. This is + useful to determine whether the passed files had any + installation data at all. */ + r = install_context_apply(&c, &paths, config_path, root_dir, force, changes, n_changes); + return r; +} + +int unit_file_disable( + UnitFileScope scope, + bool runtime, + const char *root_dir, + char *files[], + UnitFileChange **changes, + unsigned *n_changes) { + + _cleanup_lookup_paths_free_ LookupPaths paths = {}; + _cleanup_install_context_done_ InstallContext c = {}; + char **i; + _cleanup_free_ char *config_path = NULL; + _cleanup_set_free_free_ Set *remove_symlinks_to = NULL; + int r, q; + + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); + + r = lookup_paths_init_from_scope(&paths, scope); + if (r < 0) + return r; + + r = get_config_path(scope, runtime, root_dir, &config_path); + if (r < 0) + return r; + + STRV_FOREACH(i, files) { + r = install_info_add_auto(&c, *i); + if (r < 0) + return r; + } + + r = install_context_mark_for_removal(&c, &paths, &remove_symlinks_to, config_path, root_dir); + + q = remove_marked_symlinks(remove_symlinks_to, config_path, changes, n_changes, files); + if (r == 0) + r = q; + + return r; +} + +int unit_file_reenable( + UnitFileScope scope, + bool runtime, + const char *root_dir, + char *files[], + bool force, + UnitFileChange **changes, + unsigned *n_changes) { + + _cleanup_lookup_paths_free_ LookupPaths paths = {}; + _cleanup_install_context_done_ InstallContext c = {}; + char **i; + _cleanup_free_ char *config_path = NULL; + _cleanup_set_free_free_ Set *remove_symlinks_to = NULL; + int r, q; + + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); + + r = lookup_paths_init_from_scope(&paths, scope); + if (r < 0) + return r; + + r = get_config_path(scope, runtime, root_dir, &config_path); + if (r < 0) + return r; + + STRV_FOREACH(i, files) { + r = mark_symlink_for_removal(&remove_symlinks_to, *i); + if (r < 0) + return r; + + r = install_info_add_auto(&c, *i); + if (r < 0) + return r; + } + + r = remove_marked_symlinks(remove_symlinks_to, config_path, changes, n_changes, files); + + /* Returns number of symlinks that where supposed to be installed. */ + q = install_context_apply(&c, &paths, config_path, root_dir, force, changes, n_changes); + if (r == 0) + r = q; + + return r; +} + +UnitFileState unit_file_get_state( + UnitFileScope scope, + const char *root_dir, + const char *name) { + + _cleanup_lookup_paths_free_ LookupPaths paths = {}; + UnitFileState state = _UNIT_FILE_STATE_INVALID; + char **i; + _cleanup_free_ char *path = NULL; + int r; + + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); + assert(name); + + if (root_dir && scope != UNIT_FILE_SYSTEM) + return -EINVAL; + + if (!unit_name_is_valid(name, true)) + return -EINVAL; + + r = lookup_paths_init_from_scope(&paths, scope); + if (r < 0) + return r; + + STRV_FOREACH(i, paths.unit_path) { + struct stat st; + + free(path); + path = NULL; + + if (root_dir) + asprintf(&path, "%s/%s/%s", root_dir, *i, name); + else + asprintf(&path, "%s/%s", *i, name); + + if (!path) + return -ENOMEM; + + if (lstat(path, &st) < 0) { + r = -errno; + if (errno == ENOENT) + continue; + + return -errno; + } + + if (!S_ISREG(st.st_mode) && !S_ISLNK(st.st_mode)) + return -ENOENT; + + r = null_or_empty_path(path); + if (r < 0 && r != -ENOENT) + return r; + else if (r > 0) { + state = path_startswith(*i, "/run") ? + UNIT_FILE_MASKED_RUNTIME : UNIT_FILE_MASKED; + return state; + } + + r = find_symlinks_in_scope(scope, root_dir, name, &state); + if (r < 0) + return r; + else if (r > 0) + return state; + + r = unit_file_can_install(&paths, root_dir, path, true); + if (r < 0 && errno != ENOENT) + return r; + else if (r > 0) + return UNIT_FILE_DISABLED; + else if (r == 0) + return UNIT_FILE_STATIC; + } + + return r < 0 ? r : state; +} + +int unit_file_query_preset(UnitFileScope scope, const char *name) { + _cleanup_strv_free_ char **files = NULL; + char **i; + int r; + + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); + assert(name); + + if (scope == UNIT_FILE_SYSTEM) + r = conf_files_list(&files, ".preset", NULL, + "/etc/systemd/system-preset", + "/usr/local/lib/systemd/system-preset", + "/usr/lib/systemd/system-preset", +#ifdef HAVE_SPLIT_USR + "/lib/systemd/system-preset", +#endif + NULL); + else if (scope == UNIT_FILE_GLOBAL) + r = conf_files_list(&files, ".preset", NULL, + "/etc/systemd/user-preset", + "/usr/local/lib/systemd/user-preset", + "/usr/lib/systemd/user-preset", + NULL); + else + return 1; + + if (r < 0) + return r; + + STRV_FOREACH(i, files) { + _cleanup_fclose_ FILE *f; + + f = fopen(*i, "re"); + if (!f) { + if (errno == ENOENT) + continue; + + return -errno; + } + + for (;;) { + char line[LINE_MAX], *l; + + if (!fgets(line, sizeof(line), f)) + break; + + l = strstrip(line); + if (!*l) + continue; + + if (strchr(COMMENTS "\n", *l)) + continue; + + if (first_word(l, "enable")) { + l += 6; + l += strspn(l, WHITESPACE); + + if (fnmatch(l, name, FNM_NOESCAPE) == 0) + return 1; + + } else if (first_word(l, "disable")) { + l += 7; + l += strspn(l, WHITESPACE); + + if (fnmatch(l, name, FNM_NOESCAPE) == 0) + return 0; + + } else + log_debug("Couldn't parse line '%s'", l); + } + } + + /* Default is "enable" */ + return 1; +} + +int unit_file_preset( + UnitFileScope scope, + bool runtime, + const char *root_dir, + char *files[], + bool force, + UnitFileChange **changes, + unsigned *n_changes) { + + _cleanup_lookup_paths_free_ LookupPaths paths = {}; + _cleanup_install_context_done_ InstallContext plus = {}, minus = {}; + char **i; + _cleanup_free_ char *config_path = NULL; + _cleanup_set_free_free_ Set *remove_symlinks_to = NULL; + int r, q; + + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); + + r = lookup_paths_init_from_scope(&paths, scope); + if (r < 0) + return r; + + r = get_config_path(scope, runtime, root_dir, &config_path); + if (r < 0) + return r; + + STRV_FOREACH(i, files) { + + if (!unit_name_is_valid(*i, true)) + return -EINVAL; + + r = unit_file_query_preset(scope, *i); + if (r < 0) + return r; + + if (r) + r = install_info_add_auto(&plus, *i); + else + r = install_info_add_auto(&minus, *i); + + if (r < 0) + return r; + } + + r = install_context_mark_for_removal(&minus, &paths, &remove_symlinks_to, + config_path, root_dir); + + q = remove_marked_symlinks(remove_symlinks_to, config_path, + changes, n_changes, files); + if (r == 0) + r = q; + + /* Returns number of symlinks that where supposed to be installed. */ + q = install_context_apply(&plus, &paths, config_path, root_dir, force, + changes, n_changes); + if (r == 0) + r = q; + + return r; +} + +static void unitfilelist_free(UnitFileList **f) { + if (!*f) + return; + + free((*f)->path); + free(*f); +} + +int unit_file_get_list( + UnitFileScope scope, + const char *root_dir, + Hashmap *h) { + + _cleanup_lookup_paths_free_ LookupPaths paths = {}; + char **i; + _cleanup_free_ char *buf = NULL; + _cleanup_closedir_ DIR *d = NULL; + int r; + + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); + assert(h); + + if (root_dir && scope != UNIT_FILE_SYSTEM) + return -EINVAL; + + r = lookup_paths_init_from_scope(&paths, scope); + if (r < 0) + return r; + + STRV_FOREACH(i, paths.unit_path) { + const char *units_dir; + + free(buf); + buf = NULL; + + if (root_dir) { + if (asprintf(&buf, "%s/%s", root_dir, *i) < 0) + return -ENOMEM; + + units_dir = buf; + } else + units_dir = *i; + + if (d) + closedir(d); + + d = opendir(units_dir); + if (!d) { + if (errno == ENOENT) + continue; + + return -errno; + } + + for (;;) { + struct dirent *de; + union dirent_storage buffer; + UnitFileList __attribute__((cleanup(unitfilelist_free))) + *f = NULL; + + r = readdir_r(d, &buffer.de, &de); + if (r != 0) + return -r; + + if (!de) + break; + + if (ignore_file(de->d_name)) + continue; + + if (!unit_name_is_valid(de->d_name, true)) + continue; + + if (hashmap_get(h, de->d_name)) + continue; + + r = dirent_ensure_type(d, de); + if (r < 0) { + if (r == -ENOENT) + continue; + + return r; + } + + if (de->d_type != DT_LNK && de->d_type != DT_REG) + continue; + + f = new0(UnitFileList, 1); + if (!f) + return -ENOMEM; + + f->path = path_make_absolute(de->d_name, units_dir); + if (!f->path) + return -ENOMEM; + + r = null_or_empty_path(f->path); + if (r < 0 && r != -ENOENT) + return r; + else if (r > 0) { + f->state = + path_startswith(*i, "/run") ? + UNIT_FILE_MASKED_RUNTIME : UNIT_FILE_MASKED; + goto found; + } + + r = find_symlinks_in_scope(scope, root_dir, de->d_name, &f->state); + if (r < 0) + return r; + else if (r > 0) { + f->state = UNIT_FILE_ENABLED; + goto found; + } + + r = unit_file_can_install(&paths, root_dir, f->path, true); + if (r == -EINVAL || /* Invalid setting? */ + r == -EBADMSG || /* Invalid format? */ + r == -ENOENT /* Included file not found? */) + f->state = UNIT_FILE_INVALID; + else if (r < 0) + return r; + else if (r > 0) + f->state = UNIT_FILE_DISABLED; + else + f->state = UNIT_FILE_STATIC; + + found: + r = hashmap_put(h, path_get_file_name(f->path), f); + if (r < 0) + return r; + f = NULL; /* prevent cleanup */ + } + } + + return r; +} + +static const char* const unit_file_state_table[_UNIT_FILE_STATE_MAX] = { + [UNIT_FILE_ENABLED] = "enabled", + [UNIT_FILE_ENABLED_RUNTIME] = "enabled-runtime", + [UNIT_FILE_LINKED] = "linked", + [UNIT_FILE_LINKED_RUNTIME] = "linked-runtime", + [UNIT_FILE_MASKED] = "masked", + [UNIT_FILE_MASKED_RUNTIME] = "masked-runtime", + [UNIT_FILE_STATIC] = "static", + [UNIT_FILE_DISABLED] = "disabled", + [UNIT_FILE_INVALID] = "invalid", +}; + +DEFINE_STRING_TABLE_LOOKUP(unit_file_state, UnitFileState); + +static const char* const unit_file_change_type_table[_UNIT_FILE_CHANGE_TYPE_MAX] = { + [UNIT_FILE_SYMLINK] = "symlink", + [UNIT_FILE_UNLINK] = "unlink", +}; + +DEFINE_STRING_TABLE_LOOKUP(unit_file_change_type, UnitFileChangeType); diff --git a/src/shared/install.h b/src/shared/install.h new file mode 100644 index 0000000000..94516c9d05 --- /dev/null +++ b/src/shared/install.h @@ -0,0 +1,97 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "hashmap.h" + +typedef enum UnitFileScope { + UNIT_FILE_SYSTEM, + UNIT_FILE_GLOBAL, + UNIT_FILE_USER, + _UNIT_FILE_SCOPE_MAX, + _UNIT_FILE_SCOPE_INVALID = -1 +} UnitFileScope; + +typedef enum UnitFileState { + UNIT_FILE_ENABLED, + UNIT_FILE_ENABLED_RUNTIME, + UNIT_FILE_LINKED, + UNIT_FILE_LINKED_RUNTIME, + UNIT_FILE_MASKED, + UNIT_FILE_MASKED_RUNTIME, + UNIT_FILE_STATIC, + UNIT_FILE_DISABLED, + UNIT_FILE_INVALID, + _UNIT_FILE_STATE_MAX, + _UNIT_FILE_STATE_INVALID = -1 +} UnitFileState; + +typedef enum UnitFileChangeType { + UNIT_FILE_SYMLINK, + UNIT_FILE_UNLINK, + _UNIT_FILE_CHANGE_TYPE_MAX, + _UNIT_FILE_CHANGE_TYPE_INVALID = -1 +} UnitFileChangeType; + +typedef struct UnitFileChange { + UnitFileChangeType type; + char *path; + char *source; +} UnitFileChange; + +typedef struct UnitFileList { + char *path; + UnitFileState state; +} UnitFileList; + +typedef struct { + char *name; + char *path; + char *user; + + char **aliases; + char **wanted_by; + char **required_by; +} InstallInfo; + +int unit_file_enable(UnitFileScope scope, bool runtime, const char *root_dir, char *files[], bool force, UnitFileChange **changes, unsigned *n_changes); +int unit_file_disable(UnitFileScope scope, bool runtime, const char *root_dir, char *files[], UnitFileChange **changes, unsigned *n_changes); +int unit_file_reenable(UnitFileScope scope, bool runtime, const char *root_dir, char *files[], bool force, UnitFileChange **changes, unsigned *n_changes); +int unit_file_link(UnitFileScope scope, bool runtime, const char *root_dir, char *files[], bool force, UnitFileChange **changes, unsigned *n_changes); +int unit_file_preset(UnitFileScope scope, bool runtime, const char *root_dir, char *files[], bool force, UnitFileChange **changes, unsigned *n_changes); +int unit_file_mask(UnitFileScope scope, bool runtime, const char *root_dir, char *files[], bool force, UnitFileChange **changes, unsigned *n_changes); +int unit_file_unmask(UnitFileScope scope, bool runtime, const char *root_dir, char *files[], UnitFileChange **changes, unsigned *n_changes); + +UnitFileState unit_file_get_state(UnitFileScope scope, const char *root_dir, const char *filename); + +int unit_file_get_list(UnitFileScope scope, const char *root_dir, Hashmap *h); + +void unit_file_list_free(Hashmap *h); +void unit_file_changes_free(UnitFileChange *changes, unsigned n_changes); + +int unit_file_query_preset(UnitFileScope scope, const char *name); + +const char *unit_file_state_to_string(UnitFileState s) _const_; +UnitFileState unit_file_state_from_string(const char *s) _pure_; + +const char *unit_file_change_type_to_string(UnitFileChangeType s) _const_; +UnitFileChangeType unit_file_change_type_from_string(const char *s) _pure_; diff --git a/src/shared/ioprio.h b/src/shared/ioprio.h new file mode 100644 index 0000000000..9800fc2553 --- /dev/null +++ b/src/shared/ioprio.h @@ -0,0 +1,57 @@ +#ifndef IOPRIO_H +#define IOPRIO_H + +/* This is minimal version of Linux' linux/ioprio.h header file, which + * is licensed GPL2 */ + +#include <unistd.h> +#include <sys/syscall.h> + +/* + * Gives us 8 prio classes with 13-bits of data for each class + */ +#define IOPRIO_BITS (16) +#define IOPRIO_CLASS_SHIFT (13) +#define IOPRIO_PRIO_MASK ((1UL << IOPRIO_CLASS_SHIFT) - 1) + +#define IOPRIO_PRIO_CLASS(mask) ((mask) >> IOPRIO_CLASS_SHIFT) +#define IOPRIO_PRIO_DATA(mask) ((mask) & IOPRIO_PRIO_MASK) +#define IOPRIO_PRIO_VALUE(class, data) (((class) << IOPRIO_CLASS_SHIFT) | data) + +#define ioprio_valid(mask) (IOPRIO_PRIO_CLASS((mask)) != IOPRIO_CLASS_NONE) + +/* + * These are the io priority groups as implemented by CFQ. RT is the realtime + * class, it always gets premium service. BE is the best-effort scheduling + * class, the default for any process. IDLE is the idle scheduling class, it + * is only served when no one else is using the disk. + */ +enum { + IOPRIO_CLASS_NONE, + IOPRIO_CLASS_RT, + IOPRIO_CLASS_BE, + IOPRIO_CLASS_IDLE, +}; + +/* + * 8 best effort priority levels are supported + */ +#define IOPRIO_BE_NR (8) + +enum { + IOPRIO_WHO_PROCESS = 1, + IOPRIO_WHO_PGRP, + IOPRIO_WHO_USER, +}; + +static inline int ioprio_set(int which, int who, int ioprio) +{ + return syscall(__NR_ioprio_set, which, who, ioprio); +} + +static inline int ioprio_get(int which, int who) +{ + return syscall(__NR_ioprio_get, which, who); +} + +#endif diff --git a/src/shared/label.c b/src/shared/label.c new file mode 100644 index 0000000000..1fe4574633 --- /dev/null +++ b/src/shared/label.c @@ -0,0 +1,386 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <errno.h> +#include <sys/stat.h> +#include <unistd.h> +#include <malloc.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include "label.h" +#include "strv.h" +#include "util.h" +#include "path-util.h" + +#ifdef HAVE_SELINUX +#include "selinux-util.h" +#include <selinux/selinux.h> +#include <selinux/label.h> + +static struct selabel_handle *label_hnd = NULL; + +#endif + +int label_init(const char *prefix) { + int r = 0; + +#ifdef HAVE_SELINUX + usec_t before_timestamp, after_timestamp; + struct mallinfo before_mallinfo, after_mallinfo; + + if (!use_selinux()) + return 0; + + if (label_hnd) + return 0; + + before_mallinfo = mallinfo(); + before_timestamp = now(CLOCK_MONOTONIC); + + if (prefix) { + struct selinux_opt options[] = { + { .type = SELABEL_OPT_SUBSET, .value = prefix }, + }; + + label_hnd = selabel_open(SELABEL_CTX_FILE, options, ELEMENTSOF(options)); + } else + label_hnd = selabel_open(SELABEL_CTX_FILE, NULL, 0); + + if (!label_hnd) { + log_full(security_getenforce() == 1 ? LOG_ERR : LOG_DEBUG, + "Failed to initialize SELinux context: %m"); + r = security_getenforce() == 1 ? -errno : 0; + } else { + char timespan[FORMAT_TIMESPAN_MAX]; + int l; + + after_timestamp = now(CLOCK_MONOTONIC); + after_mallinfo = mallinfo(); + + l = after_mallinfo.uordblks > before_mallinfo.uordblks ? after_mallinfo.uordblks - before_mallinfo.uordblks : 0; + + log_debug("Successfully loaded SELinux database in %s, size on heap is %iK.", + format_timespan(timespan, sizeof(timespan), after_timestamp - before_timestamp, 0), + (l+1023)/1024); + } +#endif + + return r; +} + +int label_fix(const char *path, bool ignore_enoent, bool ignore_erofs) { + int r = 0; + +#ifdef HAVE_SELINUX + struct stat st; + security_context_t fcon; + + if (!use_selinux() || !label_hnd) + return 0; + + r = lstat(path, &st); + if (r == 0) { + r = selabel_lookup_raw(label_hnd, &fcon, path, st.st_mode); + + /* If there's no label to set, then exit without warning */ + if (r < 0 && errno == ENOENT) + return 0; + + if (r == 0) { + r = lsetfilecon(path, fcon); + freecon(fcon); + + /* If the FS doesn't support labels, then exit without warning */ + if (r < 0 && errno == ENOTSUP) + return 0; + } + } + + if (r < 0) { + /* Ignore ENOENT in some cases */ + if (ignore_enoent && errno == ENOENT) + return 0; + + if (ignore_erofs && errno == EROFS) + return 0; + + log_full(security_getenforce() == 1 ? LOG_ERR : LOG_DEBUG, + "Unable to fix label of %s: %m", path); + r = security_getenforce() == 1 ? -errno : 0; + } +#endif + + return r; +} + +void label_finish(void) { + +#ifdef HAVE_SELINUX + if (use_selinux() && label_hnd) + selabel_close(label_hnd); +#endif +} + +int label_get_create_label_from_exe(const char *exe, char **label) { + + int r = 0; + +#ifdef HAVE_SELINUX + security_context_t mycon = NULL, fcon = NULL; + security_class_t sclass; + + if (!use_selinux()) { + *label = NULL; + return 0; + } + + r = getcon(&mycon); + if (r < 0) + goto fail; + + r = getfilecon(exe, &fcon); + if (r < 0) + goto fail; + + sclass = string_to_security_class("process"); + r = security_compute_create(mycon, fcon, sclass, (security_context_t *) label); + if (r == 0) + log_debug("SELinux Socket context for %s will be set to %s", exe, *label); + +fail: + if (r < 0 && security_getenforce() == 1) + r = -errno; + + freecon(mycon); + freecon(fcon); +#endif + + return r; +} + +int label_context_set(const char *path, mode_t mode) { + int r = 0; + +#ifdef HAVE_SELINUX + security_context_t filecon = NULL; + + if (!use_selinux() || !label_hnd) + return 0; + + r = selabel_lookup_raw(label_hnd, &filecon, path, mode); + if (r < 0 && errno != ENOENT) + r = -errno; + else if (r == 0) { + r = setfscreatecon(filecon); + if (r < 0) { + log_error("Failed to set SELinux file context on %s: %m", path); + r = -errno; + } + + freecon(filecon); + } + + if (r < 0 && security_getenforce() == 0) + r = 0; +#endif + + return r; +} + +int label_socket_set(const char *label) { + +#ifdef HAVE_SELINUX + if (!use_selinux()) + return 0; + + if (setsockcreatecon((security_context_t) label) < 0) { + log_full(security_getenforce() == 1 ? LOG_ERR : LOG_DEBUG, + "Failed to set SELinux context (%s) on socket: %m", label); + + if (security_getenforce() == 1) + return -errno; + } +#endif + + return 0; +} + +void label_context_clear(void) { + +#ifdef HAVE_SELINUX + if (!use_selinux()) + return; + + setfscreatecon(NULL); +#endif +} + +void label_socket_clear(void) { + +#ifdef HAVE_SELINUX + if (!use_selinux()) + return; + + setsockcreatecon(NULL); +#endif +} + +void label_free(const char *label) { + +#ifdef HAVE_SELINUX + if (!use_selinux()) + return; + + freecon((security_context_t) label); +#endif +} + +int label_mkdir(const char *path, mode_t mode, bool apply) { + + /* Creates a directory and labels it according to the SELinux policy */ +#ifdef HAVE_SELINUX + int r; + security_context_t fcon = NULL; + + if (!apply || !use_selinux() || !label_hnd) + goto skipped; + + if (path_is_absolute(path)) + r = selabel_lookup_raw(label_hnd, &fcon, path, S_IFDIR); + else { + char *newpath; + + newpath = path_make_absolute_cwd(path); + if (!newpath) + return -ENOMEM; + + r = selabel_lookup_raw(label_hnd, &fcon, newpath, S_IFDIR); + free(newpath); + } + + if (r == 0) + r = setfscreatecon(fcon); + + if (r < 0 && errno != ENOENT) { + log_error("Failed to set security context %s for %s: %m", fcon, path); + + if (security_getenforce() == 1) { + r = -errno; + goto finish; + } + } + + r = mkdir(path, mode); + if (r < 0) + r = -errno; + +finish: + setfscreatecon(NULL); + freecon(fcon); + + return r; + +skipped: +#endif + return mkdir(path, mode) < 0 ? -errno : 0; +} + +int label_bind(int fd, const struct sockaddr *addr, socklen_t addrlen) { + + /* Binds a socket and label its file system object according to the SELinux policy */ + +#ifdef HAVE_SELINUX + int r; + security_context_t fcon = NULL; + const struct sockaddr_un *un; + char *path = NULL; + + assert(fd >= 0); + assert(addr); + assert(addrlen >= sizeof(sa_family_t)); + + if (!use_selinux() || !label_hnd) + goto skipped; + + /* Filter out non-local sockets */ + if (addr->sa_family != AF_UNIX) + goto skipped; + + /* Filter out anonymous sockets */ + if (addrlen < sizeof(sa_family_t) + 1) + goto skipped; + + /* Filter out abstract namespace sockets */ + un = (const struct sockaddr_un*) addr; + if (un->sun_path[0] == 0) + goto skipped; + + path = strndup(un->sun_path, addrlen - offsetof(struct sockaddr_un, sun_path)); + if (!path) + return -ENOMEM; + + if (path_is_absolute(path)) + r = selabel_lookup_raw(label_hnd, &fcon, path, S_IFSOCK); + else { + char *newpath; + + newpath = path_make_absolute_cwd(path); + + if (!newpath) { + free(path); + return -ENOMEM; + } + + r = selabel_lookup_raw(label_hnd, &fcon, newpath, S_IFSOCK); + free(newpath); + } + + if (r == 0) + r = setfscreatecon(fcon); + + if (r < 0 && errno != ENOENT) { + log_error("Failed to set security context %s for %s: %m", fcon, path); + + if (security_getenforce() == 1) { + r = -errno; + goto finish; + } + } + + r = bind(fd, addr, addrlen); + if (r < 0) + r = -errno; + +finish: + setfscreatecon(NULL); + freecon(fcon); + free(path); + + return r; + +skipped: +#endif + return bind(fd, addr, addrlen) < 0 ? -errno : 0; +} diff --git a/src/shared/label.h b/src/shared/label.h new file mode 100644 index 0000000000..dda4d1c024 --- /dev/null +++ b/src/shared/label.h @@ -0,0 +1,51 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <sys/types.h> +#include <stdbool.h> +#include <sys/socket.h> + +int label_init(const char *prefix); +void label_finish(void); + +int label_fix(const char *path, bool ignore_enoent, bool ignore_erofs); + +int label_socket_set(const char *label); +void label_socket_clear(void); + +int label_context_set(const char *path, mode_t mode); +void label_context_clear(void); + +void label_free(const char *label); + +int label_get_create_label_from_exe(const char *exe, char **label); + +int label_mkdir(const char *path, mode_t mode, bool apply); + +void label_retest_selinux(void); + +int label_bind(int fd, const struct sockaddr *addr, socklen_t addrlen); + +int label_write_one_line_file_atomic(const char *fn, const char *line); +int label_write_env_file(const char *fname, char **l); +int label_fopen_temporary(const char *path, FILE **_f, char **_temp_path); diff --git a/src/shared/linux/auto_dev-ioctl.h b/src/shared/linux/auto_dev-ioctl.h new file mode 100644 index 0000000000..850f39b33e --- /dev/null +++ b/src/shared/linux/auto_dev-ioctl.h @@ -0,0 +1,229 @@ +/* + * Copyright 2008 Red Hat, Inc. All rights reserved. + * Copyright 2008 Ian Kent <raven@themaw.net> + * + * This file is part of the Linux kernel and is made available under + * the terms of the GNU General Public License, version 2, or at your + * option, any later version, incorporated herein by reference. + */ + +#ifndef _LINUX_AUTO_DEV_IOCTL_H +#define _LINUX_AUTO_DEV_IOCTL_H + +#include <linux/auto_fs.h> + +#ifdef __KERNEL__ +#include <linux/string.h> +#else +#include <string.h> +#endif /* __KERNEL__ */ + +#define AUTOFS_DEVICE_NAME "autofs" + +#define AUTOFS_DEV_IOCTL_VERSION_MAJOR 1 +#define AUTOFS_DEV_IOCTL_VERSION_MINOR 0 + +#define AUTOFS_DEVID_LEN 16 + +#define AUTOFS_DEV_IOCTL_SIZE sizeof(struct autofs_dev_ioctl) + +/* + * An ioctl interface for autofs mount point control. + */ + +struct args_protover { + __u32 version; +}; + +struct args_protosubver { + __u32 sub_version; +}; + +struct args_openmount { + __u32 devid; +}; + +struct args_ready { + __u32 token; +}; + +struct args_fail { + __u32 token; + __s32 status; +}; + +struct args_setpipefd { + __s32 pipefd; +}; + +struct args_timeout { + __u64 timeout; +}; + +struct args_requester { + __u32 uid; + __u32 gid; +}; + +struct args_expire { + __u32 how; +}; + +struct args_askumount { + __u32 may_umount; +}; + +struct args_ismountpoint { + union { + struct args_in { + __u32 type; + } in; + struct args_out { + __u32 devid; + __u32 magic; + } out; + }; +}; + +/* + * All the ioctls use this structure. + * When sending a path size must account for the total length + * of the chunk of memory otherwise is is the size of the + * structure. + */ + +struct autofs_dev_ioctl { + __u32 ver_major; + __u32 ver_minor; + __u32 size; /* total size of data passed in + * including this struct */ + __s32 ioctlfd; /* automount command fd */ + + /* Command parameters */ + + union { + struct args_protover protover; + struct args_protosubver protosubver; + struct args_openmount openmount; + struct args_ready ready; + struct args_fail fail; + struct args_setpipefd setpipefd; + struct args_timeout timeout; + struct args_requester requester; + struct args_expire expire; + struct args_askumount askumount; + struct args_ismountpoint ismountpoint; + }; + + char path[0]; +}; + +static inline void init_autofs_dev_ioctl(struct autofs_dev_ioctl *in) +{ + memset(in, 0, sizeof(struct autofs_dev_ioctl)); + in->ver_major = AUTOFS_DEV_IOCTL_VERSION_MAJOR; + in->ver_minor = AUTOFS_DEV_IOCTL_VERSION_MINOR; + in->size = sizeof(struct autofs_dev_ioctl); + in->ioctlfd = -1; + return; +} + +/* + * If you change this make sure you make the corresponding change + * to autofs-dev-ioctl.c:lookup_ioctl() + */ +enum { + /* Get various version info */ + AUTOFS_DEV_IOCTL_VERSION_CMD = 0x71, + AUTOFS_DEV_IOCTL_PROTOVER_CMD, + AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD, + + /* Open mount ioctl fd */ + AUTOFS_DEV_IOCTL_OPENMOUNT_CMD, + + /* Close mount ioctl fd */ + AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD, + + /* Mount/expire status returns */ + AUTOFS_DEV_IOCTL_READY_CMD, + AUTOFS_DEV_IOCTL_FAIL_CMD, + + /* Activate/deactivate autofs mount */ + AUTOFS_DEV_IOCTL_SETPIPEFD_CMD, + AUTOFS_DEV_IOCTL_CATATONIC_CMD, + + /* Expiry timeout */ + AUTOFS_DEV_IOCTL_TIMEOUT_CMD, + + /* Get mount last requesting uid and gid */ + AUTOFS_DEV_IOCTL_REQUESTER_CMD, + + /* Check for eligible expire candidates */ + AUTOFS_DEV_IOCTL_EXPIRE_CMD, + + /* Request busy status */ + AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD, + + /* Check if path is a mountpoint */ + AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD, +}; + +#define AUTOFS_IOCTL 0x93 + +#define AUTOFS_DEV_IOCTL_VERSION \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_VERSION_CMD, struct autofs_dev_ioctl) + +#define AUTOFS_DEV_IOCTL_PROTOVER \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_PROTOVER_CMD, struct autofs_dev_ioctl) + +#define AUTOFS_DEV_IOCTL_PROTOSUBVER \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD, struct autofs_dev_ioctl) + +#define AUTOFS_DEV_IOCTL_OPENMOUNT \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_OPENMOUNT_CMD, struct autofs_dev_ioctl) + +#define AUTOFS_DEV_IOCTL_CLOSEMOUNT \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD, struct autofs_dev_ioctl) + +#define AUTOFS_DEV_IOCTL_READY \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_READY_CMD, struct autofs_dev_ioctl) + +#define AUTOFS_DEV_IOCTL_FAIL \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_FAIL_CMD, struct autofs_dev_ioctl) + +#define AUTOFS_DEV_IOCTL_SETPIPEFD \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_SETPIPEFD_CMD, struct autofs_dev_ioctl) + +#define AUTOFS_DEV_IOCTL_CATATONIC \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_CATATONIC_CMD, struct autofs_dev_ioctl) + +#define AUTOFS_DEV_IOCTL_TIMEOUT \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_TIMEOUT_CMD, struct autofs_dev_ioctl) + +#define AUTOFS_DEV_IOCTL_REQUESTER \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_REQUESTER_CMD, struct autofs_dev_ioctl) + +#define AUTOFS_DEV_IOCTL_EXPIRE \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_EXPIRE_CMD, struct autofs_dev_ioctl) + +#define AUTOFS_DEV_IOCTL_ASKUMOUNT \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD, struct autofs_dev_ioctl) + +#define AUTOFS_DEV_IOCTL_ISMOUNTPOINT \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD, struct autofs_dev_ioctl) + +#endif /* _LINUX_AUTO_DEV_IOCTL_H */ diff --git a/src/shared/linux/fanotify.h b/src/shared/linux/fanotify.h new file mode 100644 index 0000000000..63531a6b4d --- /dev/null +++ b/src/shared/linux/fanotify.h @@ -0,0 +1,98 @@ +#ifndef _LINUX_FANOTIFY_H +#define _LINUX_FANOTIFY_H + +#include <linux/types.h> + +/* the following events that user-space can register for */ +#define FAN_ACCESS 0x00000001 /* File was accessed */ +#define FAN_MODIFY 0x00000002 /* File was modified */ +#define FAN_CLOSE_WRITE 0x00000008 /* Unwrittable file closed */ +#define FAN_CLOSE_NOWRITE 0x00000010 /* Writtable file closed */ +#define FAN_OPEN 0x00000020 /* File was opened */ + +#define FAN_EVENT_ON_CHILD 0x08000000 /* interested in child events */ + +/* FIXME currently Q's have no limit.... */ +#define FAN_Q_OVERFLOW 0x00004000 /* Event queued overflowed */ + +#define FAN_OPEN_PERM 0x00010000 /* File open in perm check */ +#define FAN_ACCESS_PERM 0x00020000 /* File accessed in perm check */ + +/* helper events */ +#define FAN_CLOSE (FAN_CLOSE_WRITE | FAN_CLOSE_NOWRITE) /* close */ + +/* flags used for fanotify_init() */ +#define FAN_CLOEXEC 0x00000001 +#define FAN_NONBLOCK 0x00000002 + +#define FAN_ALL_INIT_FLAGS (FAN_CLOEXEC | FAN_NONBLOCK) + +/* flags used for fanotify_modify_mark() */ +#define FAN_MARK_ADD 0x00000001 +#define FAN_MARK_REMOVE 0x00000002 +#define FAN_MARK_DONT_FOLLOW 0x00000004 +#define FAN_MARK_ONLYDIR 0x00000008 +#define FAN_MARK_MOUNT 0x00000010 +#define FAN_MARK_IGNORED_MASK 0x00000020 +#define FAN_MARK_IGNORED_SURV_MODIFY 0x00000040 +#define FAN_MARK_FLUSH 0x00000080 + +#define FAN_ALL_MARK_FLAGS (FAN_MARK_ADD |\ + FAN_MARK_REMOVE |\ + FAN_MARK_DONT_FOLLOW |\ + FAN_MARK_ONLYDIR |\ + FAN_MARK_MOUNT |\ + FAN_MARK_IGNORED_MASK |\ + FAN_MARK_IGNORED_SURV_MODIFY) + +/* + * All of the events - we build the list by hand so that we can add flags in + * the future and not break backward compatibility. Apps will get only the + * events that they originally wanted. Be sure to add new events here! + */ +#define FAN_ALL_EVENTS (FAN_ACCESS |\ + FAN_MODIFY |\ + FAN_CLOSE |\ + FAN_OPEN) + +/* + * All events which require a permission response from userspace + */ +#define FAN_ALL_PERM_EVENTS (FAN_OPEN_PERM |\ + FAN_ACCESS_PERM) + +#define FAN_ALL_OUTGOING_EVENTS (FAN_ALL_EVENTS |\ + FAN_ALL_PERM_EVENTS |\ + FAN_Q_OVERFLOW) + +#define FANOTIFY_METADATA_VERSION 2 + +struct fanotify_event_metadata { + __u32 event_len; + __u32 vers; + __u64 mask; + __s32 fd; + __s32 pid; +} __attribute__ ((packed)); + +struct fanotify_response { + __s32 fd; + __u32 response; +} __attribute__ ((packed)); + +/* Legit userspace responses to a _PERM event */ +#define FAN_ALLOW 0x01 +#define FAN_DENY 0x02 + +/* Helper functions to deal with fanotify_event_metadata buffers */ +#define FAN_EVENT_METADATA_LEN (sizeof(struct fanotify_event_metadata)) + +#define FAN_EVENT_NEXT(meta, len) ((len) -= (meta)->event_len, \ + (struct fanotify_event_metadata*)(((char *)(meta)) + \ + (meta)->event_len)) + +#define FAN_EVENT_OK(meta, len) ((long)(len) >= (long)FAN_EVENT_METADATA_LEN && \ + (long)(meta)->event_len >= (long)FAN_EVENT_METADATA_LEN && \ + (long)(meta)->event_len <= (long)(len)) + +#endif /* _LINUX_FANOTIFY_H */ diff --git a/src/shared/linux/seccomp-bpf.h b/src/shared/linux/seccomp-bpf.h new file mode 100644 index 0000000000..1e3d136739 --- /dev/null +++ b/src/shared/linux/seccomp-bpf.h @@ -0,0 +1,76 @@ +/* + * seccomp example for x86 (32-bit and 64-bit) with BPF macros + * + * Copyright (c) 2012 The Chromium OS Authors <chromium-os-dev@chromium.org> + * Authors: + * Will Drewry <wad@chromium.org> + * Kees Cook <keescook@chromium.org> + * + * The code may be used by anyone for any purpose, and can serve as a + * starting point for developing applications using mode 2 seccomp. + */ +#ifndef _SECCOMP_BPF_H_ +#define _SECCOMP_BPF_H_ + +#include <stdio.h> +#include <stddef.h> +#include <stdlib.h> +#include <errno.h> +#include <signal.h> +#include <string.h> +#include <unistd.h> + +#include <sys/prctl.h> + +#include <linux/unistd.h> +#include <linux/audit.h> +#include <linux/filter.h> +#include <linux/seccomp.h> + +#ifndef SECCOMP_MODE_FILTER +# define SECCOMP_MODE_FILTER 2 /* uses user-supplied filter. */ +# define SECCOMP_RET_KILL 0x00000000U /* kill the task immediately */ +# define SECCOMP_RET_TRAP 0x00030000U /* disallow and force a SIGSYS */ +# define SECCOMP_RET_ALLOW 0x7fff0000U /* allow */ +struct seccomp_data { + int nr; + __u32 arch; + __u64 instruction_pointer; + __u64 args[6]; +}; +#endif +#ifndef SYS_SECCOMP +# define SYS_SECCOMP 1 +#endif + +#define syscall_nr (offsetof(struct seccomp_data, nr)) +#define arch_nr (offsetof(struct seccomp_data, arch)) + +#if defined(__i386__) +# define REG_SYSCALL REG_EAX +# define ARCH_NR AUDIT_ARCH_I386 +#elif defined(__x86_64__) +# define REG_SYSCALL REG_RAX +# define ARCH_NR AUDIT_ARCH_X86_64 +#else +# warning "Platform does not support seccomp filter yet" +# define REG_SYSCALL 0 +# define ARCH_NR 0 +#endif + +#define VALIDATE_ARCHITECTURE \ + BPF_STMT(BPF_LD+BPF_W+BPF_ABS, arch_nr), \ + BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, ARCH_NR, 1, 0), \ + BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_KILL) + +#define EXAMINE_SYSCALL \ + BPF_STMT(BPF_LD+BPF_W+BPF_ABS, syscall_nr) + +#define ALLOW_SYSCALL(name) \ + BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, __NR_##name, 0, 1), \ + BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW) + +#define _KILL_PROCESS \ + BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_KILL) + +#endif /* _SECCOMP_BPF_H_ */ diff --git a/src/shared/linux/seccomp.h b/src/shared/linux/seccomp.h new file mode 100644 index 0000000000..9c03683fa0 --- /dev/null +++ b/src/shared/linux/seccomp.h @@ -0,0 +1,47 @@ +#ifndef _LINUX_SECCOMP_H +#define _LINUX_SECCOMP_H + + +#include <linux/types.h> + + +/* Valid values for seccomp.mode and prctl(PR_SET_SECCOMP, <mode>) */ +#define SECCOMP_MODE_DISABLED 0 /* seccomp is not in use. */ +#define SECCOMP_MODE_STRICT 1 /* uses hard-coded filter. */ +#define SECCOMP_MODE_FILTER 2 /* uses user-supplied filter. */ + +/* + * All BPF programs must return a 32-bit value. + * The bottom 16-bits are for optional return data. + * The upper 16-bits are ordered from least permissive values to most. + * + * The ordering ensures that a min_t() over composed return values always + * selects the least permissive choice. + */ +#define SECCOMP_RET_KILL 0x00000000U /* kill the task immediately */ +#define SECCOMP_RET_TRAP 0x00030000U /* disallow and force a SIGSYS */ +#define SECCOMP_RET_ERRNO 0x00050000U /* returns an errno */ +#define SECCOMP_RET_TRACE 0x7ff00000U /* pass to a tracer or disallow */ +#define SECCOMP_RET_ALLOW 0x7fff0000U /* allow */ + +/* Masks for the return value sections. */ +#define SECCOMP_RET_ACTION 0x7fff0000U +#define SECCOMP_RET_DATA 0x0000ffffU + +/** + * struct seccomp_data - the format the BPF program executes over. + * @nr: the system call number + * @arch: indicates system call convention as an AUDIT_ARCH_* value + * as defined in <linux/audit.h>. + * @instruction_pointer: at the time of the system call. + * @args: up to 6 system call arguments always stored as 64-bit values + * regardless of the architecture. + */ +struct seccomp_data { + int nr; + __u32 arch; + __u64 instruction_pointer; + __u64 args[6]; +}; + +#endif /* _LINUX_SECCOMP_H */ diff --git a/src/shared/list.h b/src/shared/list.h new file mode 100644 index 0000000000..47f275a019 --- /dev/null +++ b/src/shared/list.h @@ -0,0 +1,125 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +/* The head of the linked list. Use this in the structure that shall + * contain the head of the linked list */ +#define LIST_HEAD(t,name) \ + t *name + +/* The pointers in the linked list's items. Use this in the item structure */ +#define LIST_FIELDS(t,name) \ + t *name##_next, *name##_prev + +/* Initialize the list's head */ +#define LIST_HEAD_INIT(t,head) \ + do { \ + (head) = NULL; } \ + while(false) + +/* Initialize a list item */ +#define LIST_INIT(t,name,item) \ + do { \ + t *_item = (item); \ + assert(_item); \ + _item->name##_prev = _item->name##_next = NULL; \ + } while(false) + +/* Prepend an item to the list */ +#define LIST_PREPEND(t,name,head,item) \ + do { \ + t **_head = &(head), *_item = (item); \ + assert(_item); \ + if ((_item->name##_next = *_head)) \ + _item->name##_next->name##_prev = _item; \ + _item->name##_prev = NULL; \ + *_head = _item; \ + } while(false) + +/* Remove an item from the list */ +#define LIST_REMOVE(t,name,head,item) \ + do { \ + t **_head = &(head), *_item = (item); \ + assert(_item); \ + if (_item->name##_next) \ + _item->name##_next->name##_prev = _item->name##_prev; \ + if (_item->name##_prev) \ + _item->name##_prev->name##_next = _item->name##_next; \ + else { \ + assert(*_head == _item); \ + *_head = _item->name##_next; \ + } \ + _item->name##_next = _item->name##_prev = NULL; \ + } while(false) + +/* Find the head of the list */ +#define LIST_FIND_HEAD(t,name,item,head) \ + do { \ + t *_item = (item); \ + assert(_item); \ + while (_item->name##_prev) \ + _item = _item->name##_prev; \ + (head) = _item; \ + } while (false) + +/* Find the head of the list */ +#define LIST_FIND_TAIL(t,name,item,tail) \ + do { \ + t *_item = (item); \ + assert(_item); \ + while (_item->name##_next) \ + _item = _item->name##_next; \ + (tail) = _item; \ + } while (false) + +/* Insert an item after another one (a = where, b = what) */ +#define LIST_INSERT_AFTER(t,name,head,a,b) \ + do { \ + t **_head = &(head), *_a = (a), *_b = (b); \ + assert(_b); \ + if (!_a) { \ + if ((_b->name##_next = *_head)) \ + _b->name##_next->name##_prev = _b; \ + _b->name##_prev = NULL; \ + *_head = _b; \ + } else { \ + if ((_b->name##_next = _a->name##_next)) \ + _b->name##_next->name##_prev = _b; \ + _b->name##_prev = _a; \ + _a->name##_next = _b; \ + } \ + } while(false) + +#define LIST_JUST_US(name,item) \ + (!(item)->name##_prev && !(item)->name##_next) \ + +#define LIST_FOREACH(name,i,head) \ + for ((i) = (head); (i); (i) = (i)->name##_next) + +#define LIST_FOREACH_SAFE(name,i,n,head) \ + for ((i) = (head); (i) && (((n) = (i)->name##_next), 1); (i) = (n)) + +#define LIST_FOREACH_BEFORE(name,i,p) \ + for ((i) = (p)->name##_prev; (i); (i) = (i)->name##_prev) + +#define LIST_FOREACH_AFTER(name,i,p) \ + for ((i) = (p)->name##_next; (i); (i) = (i)->name##_next) diff --git a/src/shared/log.c b/src/shared/log.c new file mode 100644 index 0000000000..27317f7ed3 --- /dev/null +++ b/src/shared/log.c @@ -0,0 +1,927 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <stdarg.h> +#include <stdio.h> +#include <errno.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <stddef.h> +#include <printf.h> + +#include "log.h" +#include "util.h" +#include "missing.h" +#include "macro.h" +#include "socket-util.h" + +#define SNDBUF_SIZE (8*1024*1024) + +static LogTarget log_target = LOG_TARGET_CONSOLE; +static int log_max_level = LOG_INFO; +static int log_facility = LOG_DAEMON; + +static int console_fd = STDERR_FILENO; +static int syslog_fd = -1; +static int kmsg_fd = -1; +static int journal_fd = -1; + +static bool syslog_is_stream = false; + +static bool show_color = false; +static bool show_location = false; + +/* Akin to glibc's __abort_msg; which is private and we hence cannot + * use here. */ +static char *log_abort_msg = NULL; + +void log_close_console(void) { + + if (console_fd < 0) + return; + + if (getpid() == 1) { + if (console_fd >= 3) + close_nointr_nofail(console_fd); + + console_fd = -1; + } +} + +static int log_open_console(void) { + + if (console_fd >= 0) + return 0; + + if (getpid() == 1) { + console_fd = open_terminal("/dev/console", O_WRONLY|O_NOCTTY|O_CLOEXEC); + if (console_fd < 0) + return console_fd; + } else + console_fd = STDERR_FILENO; + + return 0; +} + +void log_close_kmsg(void) { + + if (kmsg_fd < 0) + return; + + close_nointr_nofail(kmsg_fd); + kmsg_fd = -1; +} + +static int log_open_kmsg(void) { + + if (kmsg_fd >= 0) + return 0; + + kmsg_fd = open("/dev/kmsg", O_WRONLY|O_NOCTTY|O_CLOEXEC); + if (kmsg_fd < 0) + return -errno; + + return 0; +} + +void log_close_syslog(void) { + + if (syslog_fd < 0) + return; + + close_nointr_nofail(syslog_fd); + syslog_fd = -1; +} + +static int create_log_socket(int type) { + int fd; + + /* All output to the syslog/journal fds we do asynchronously, + * and if the buffers are full we just drop the messages */ + + fd = socket(AF_UNIX, type|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (fd < 0) + return -errno; + + fd_inc_sndbuf(fd, SNDBUF_SIZE); + + return fd; +} + +static int log_open_syslog(void) { + int r; + union sockaddr_union sa = { + .un.sun_family = AF_UNIX, + .un.sun_path = "/dev/log", + }; + + if (syslog_fd >= 0) + return 0; + + syslog_fd = create_log_socket(SOCK_DGRAM); + if (syslog_fd < 0) { + r = syslog_fd; + goto fail; + } + + if (connect(syslog_fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path)) < 0) { + close_nointr_nofail(syslog_fd); + + /* Some legacy syslog systems still use stream + * sockets. They really shouldn't. But what can we + * do... */ + syslog_fd = create_log_socket(SOCK_STREAM); + if (syslog_fd < 0) { + r = syslog_fd; + goto fail; + } + + if (connect(syslog_fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path)) < 0) { + r = -errno; + goto fail; + } + + syslog_is_stream = true; + } else + syslog_is_stream = false; + + return 0; + +fail: + log_close_syslog(); + return r; +} + +void log_close_journal(void) { + + if (journal_fd < 0) + return; + + close_nointr_nofail(journal_fd); + journal_fd = -1; +} + +static int log_open_journal(void) { + union sockaddr_union sa = { + .un.sun_family = AF_UNIX, + .un.sun_path = "/run/systemd/journal/socket", + }; + int r; + + if (journal_fd >= 0) + return 0; + + journal_fd = create_log_socket(SOCK_DGRAM); + if (journal_fd < 0) { + r = journal_fd; + goto fail; + } + + if (connect(journal_fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path)) < 0) { + r = -errno; + goto fail; + } + + return 0; + +fail: + log_close_journal(); + return r; +} + +int log_open(void) { + int r; + + /* If we don't use the console we close it here, to not get + * killed by SAK. If we don't use syslog we close it here so + * that we are not confused by somebody deleting the socket in + * the fs. If we don't use /dev/kmsg we still keep it open, + * because there is no reason to close it. */ + + if (log_target == LOG_TARGET_NULL) { + log_close_journal(); + log_close_syslog(); + log_close_console(); + return 0; + } + + if ((log_target != LOG_TARGET_AUTO && log_target != LOG_TARGET_SAFE) || + getpid() == 1 || + isatty(STDERR_FILENO) <= 0) { + + if (log_target == LOG_TARGET_AUTO || + log_target == LOG_TARGET_JOURNAL_OR_KMSG || + log_target == LOG_TARGET_JOURNAL) { + r = log_open_journal(); + if (r >= 0) { + log_close_syslog(); + log_close_console(); + return r; + } + } + + if (log_target == LOG_TARGET_SYSLOG_OR_KMSG || + log_target == LOG_TARGET_SYSLOG) { + r = log_open_syslog(); + if (r >= 0) { + log_close_journal(); + log_close_console(); + return r; + } + } + + if (log_target == LOG_TARGET_AUTO || + log_target == LOG_TARGET_SAFE || + log_target == LOG_TARGET_JOURNAL_OR_KMSG || + log_target == LOG_TARGET_SYSLOG_OR_KMSG || + log_target == LOG_TARGET_KMSG) { + r = log_open_kmsg(); + if (r >= 0) { + log_close_journal(); + log_close_syslog(); + log_close_console(); + return r; + } + } + } + + log_close_journal(); + log_close_syslog(); + + /* Get the real /dev/console if we are PID=1, hence reopen */ + log_close_console(); + return log_open_console(); +} + +void log_set_target(LogTarget target) { + assert(target >= 0); + assert(target < _LOG_TARGET_MAX); + + log_target = target; +} + +void log_close(void) { + log_close_journal(); + log_close_syslog(); + log_close_kmsg(); + log_close_console(); +} + +void log_forget_fds(void) { + console_fd = kmsg_fd = syslog_fd = journal_fd = -1; +} + +void log_set_max_level(int level) { + assert((level & LOG_PRIMASK) == level); + + log_max_level = level; +} + +void log_set_facility(int facility) { + log_facility = facility; +} + +static int write_to_console( + int level, + const char*file, + int line, + const char *func, + const char *object_name, + const char *object, + const char *buffer) { + + char location[64]; + struct iovec iovec[5] = {}; + unsigned n = 0; + bool highlight; + + if (console_fd < 0) + return 0; + + highlight = LOG_PRI(level) <= LOG_ERR && show_color; + + if (show_location) { + snprintf(location, sizeof(location), "(%s:%u) ", file, line); + char_array_0(location); + IOVEC_SET_STRING(iovec[n++], location); + } + + if (highlight) + IOVEC_SET_STRING(iovec[n++], ANSI_HIGHLIGHT_RED_ON); + IOVEC_SET_STRING(iovec[n++], buffer); + if (highlight) + IOVEC_SET_STRING(iovec[n++], ANSI_HIGHLIGHT_OFF); + IOVEC_SET_STRING(iovec[n++], "\n"); + + if (writev(console_fd, iovec, n) < 0) + return -errno; + + return 1; +} + +static int write_to_syslog( + int level, + const char*file, + int line, + const char *func, + const char *object_name, + const char *object, + const char *buffer) { + + char header_priority[16], header_time[64], header_pid[16]; + struct iovec iovec[5] = {}; + struct msghdr msghdr = { + .msg_iov = iovec, + .msg_iovlen = ELEMENTSOF(iovec), + }; + time_t t; + struct tm *tm; + + if (syslog_fd < 0) + return 0; + + snprintf(header_priority, sizeof(header_priority), "<%i>", level); + char_array_0(header_priority); + + t = (time_t) (now(CLOCK_REALTIME) / USEC_PER_SEC); + tm = localtime(&t); + if (!tm) + return -EINVAL; + + if (strftime(header_time, sizeof(header_time), "%h %e %T ", tm) <= 0) + return -EINVAL; + + snprintf(header_pid, sizeof(header_pid), "[%lu]: ", (unsigned long) getpid()); + char_array_0(header_pid); + + IOVEC_SET_STRING(iovec[0], header_priority); + IOVEC_SET_STRING(iovec[1], header_time); + IOVEC_SET_STRING(iovec[2], program_invocation_short_name); + IOVEC_SET_STRING(iovec[3], header_pid); + IOVEC_SET_STRING(iovec[4], buffer); + + /* When using syslog via SOCK_STREAM separate the messages by NUL chars */ + if (syslog_is_stream) + iovec[4].iov_len++; + + for (;;) { + ssize_t n; + + n = sendmsg(syslog_fd, &msghdr, MSG_NOSIGNAL); + if (n < 0) + return -errno; + + if (!syslog_is_stream || + (size_t) n >= IOVEC_TOTAL_SIZE(iovec, ELEMENTSOF(iovec))) + break; + + IOVEC_INCREMENT(iovec, ELEMENTSOF(iovec), n); + } + + return 1; +} + +static int write_to_kmsg( + int level, + const char*file, + int line, + const char *func, + const char *object_name, + const char *object, + const char *buffer) { + + char header_priority[16], header_pid[16]; + struct iovec iovec[5] = {}; + + if (kmsg_fd < 0) + return 0; + + snprintf(header_priority, sizeof(header_priority), "<%i>", level); + char_array_0(header_priority); + + snprintf(header_pid, sizeof(header_pid), "[%lu]: ", (unsigned long) getpid()); + char_array_0(header_pid); + + IOVEC_SET_STRING(iovec[0], header_priority); + IOVEC_SET_STRING(iovec[1], program_invocation_short_name); + IOVEC_SET_STRING(iovec[2], header_pid); + IOVEC_SET_STRING(iovec[3], buffer); + IOVEC_SET_STRING(iovec[4], "\n"); + + if (writev(kmsg_fd, iovec, ELEMENTSOF(iovec)) < 0) + return -errno; + + return 1; +} + +static int log_do_header(char *header, size_t size, + int level, + const char *file, int line, const char *func, + const char *object_name, const char *object) { + snprintf(header, size, + "PRIORITY=%i\n" + "SYSLOG_FACILITY=%i\n" + "%s%.*s%s" + "%s%.*i%s" + "%s%.*s%s" + "%s%.*s%s" + "SYSLOG_IDENTIFIER=%s\n", + LOG_PRI(level), + LOG_FAC(level), + file ? "CODE_FILE=" : "", + file ? LINE_MAX : 0, file, /* %.0s means no output */ + file ? "\n" : "", + line ? "CODE_LINE=" : "", + line ? 1 : 0, line, /* %.0d means no output too, special case for 0 */ + line ? "\n" : "", + func ? "CODE_FUNCTION=" : "", + func ? LINE_MAX : 0, func, + func ? "\n" : "", + object ? object_name : "", + object ? LINE_MAX : 0, object, /* %.0s means no output */ + object ? "\n" : "", + program_invocation_short_name); + header[size - 1] = '\0'; + return 0; +} + +static int write_to_journal( + int level, + const char*file, + int line, + const char *func, + const char *object_name, + const char *object, + const char *buffer) { + + char header[LINE_MAX]; + struct iovec iovec[4] = {}; + struct msghdr mh = {}; + + if (journal_fd < 0) + return 0; + + log_do_header(header, sizeof(header), level, + file, line, func, object_name, object); + + IOVEC_SET_STRING(iovec[0], header); + IOVEC_SET_STRING(iovec[1], "MESSAGE="); + IOVEC_SET_STRING(iovec[2], buffer); + IOVEC_SET_STRING(iovec[3], "\n"); + + mh.msg_iov = iovec; + mh.msg_iovlen = ELEMENTSOF(iovec); + + if (sendmsg(journal_fd, &mh, MSG_NOSIGNAL) < 0) + return -errno; + + return 1; +} + +static int log_dispatch( + int level, + const char*file, + int line, + const char *func, + const char *object_name, + const char *object, + char *buffer) { + + int r = 0; + + if (log_target == LOG_TARGET_NULL) + return 0; + + /* Patch in LOG_DAEMON facility if necessary */ + if ((level & LOG_FACMASK) == 0) + level = log_facility | LOG_PRI(level); + + do { + char *e; + int k = 0; + + buffer += strspn(buffer, NEWLINE); + + if (buffer[0] == 0) + break; + + if ((e = strpbrk(buffer, NEWLINE))) + *(e++) = 0; + + if (log_target == LOG_TARGET_AUTO || + log_target == LOG_TARGET_JOURNAL_OR_KMSG || + log_target == LOG_TARGET_JOURNAL) { + + k = write_to_journal(level, file, line, func, + object_name, object, buffer); + if (k < 0) { + if (k != -EAGAIN) + log_close_journal(); + log_open_kmsg(); + } else if (k > 0) + r++; + } + + if (log_target == LOG_TARGET_SYSLOG_OR_KMSG || + log_target == LOG_TARGET_SYSLOG) { + + k = write_to_syslog(level, file, line, func, + object_name, object, buffer); + if (k < 0) { + if (k != -EAGAIN) + log_close_syslog(); + log_open_kmsg(); + } else if (k > 0) + r++; + } + + if (k <= 0 && + (log_target == LOG_TARGET_AUTO || + log_target == LOG_TARGET_SAFE || + log_target == LOG_TARGET_SYSLOG_OR_KMSG || + log_target == LOG_TARGET_JOURNAL_OR_KMSG || + log_target == LOG_TARGET_KMSG)) { + + k = write_to_kmsg(level, file, line, func, + object_name, object, buffer); + if (k < 0) { + log_close_kmsg(); + log_open_console(); + } else if (k > 0) + r++; + } + + if (k <= 0) { + k = write_to_console(level, file, line, func, + object_name, object, buffer); + if (k < 0) + return k; + } + + buffer = e; + } while (buffer); + + return r; +} + +int log_dump_internal( + int level, + const char*file, + int line, + const char *func, + char *buffer) { + + PROTECT_ERRNO; + + /* This modifies the buffer... */ + + if (_likely_(LOG_PRI(level) > log_max_level)) + return 0; + + return log_dispatch(level, file, line, func, NULL, NULL, buffer); +} + +int log_metav( + int level, + const char*file, + int line, + const char *func, + const char *format, + va_list ap) { + + PROTECT_ERRNO; + char buffer[LINE_MAX]; + + if (_likely_(LOG_PRI(level) > log_max_level)) + return 0; + + vsnprintf(buffer, sizeof(buffer), format, ap); + char_array_0(buffer); + + return log_dispatch(level, file, line, func, NULL, NULL, buffer); +} + +int log_meta( + int level, + const char*file, + int line, + const char *func, + const char *format, ...) { + + int r; + va_list ap; + + va_start(ap, format); + r = log_metav(level, file, line, func, format, ap); + va_end(ap); + + return r; +} + +int log_metav_object( + int level, + const char*file, + int line, + const char *func, + const char *object_name, + const char *object, + const char *format, + va_list ap) { + + PROTECT_ERRNO; + char buffer[LINE_MAX]; + + if (_likely_(LOG_PRI(level) > log_max_level)) + return 0; + + vsnprintf(buffer, sizeof(buffer), format, ap); + char_array_0(buffer); + + return log_dispatch(level, file, line, func, + object_name, object, buffer); +} + +int log_meta_object( + int level, + const char*file, + int line, + const char *func, + const char *object_name, + const char *object, + const char *format, ...) { + + int r; + va_list ap; + + va_start(ap, format); + r = log_metav_object(level, file, line, func, + object_name, object, format, ap); + va_end(ap); + + return r; +} + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-nonliteral" +_noreturn_ static void log_assert(const char *text, const char *file, int line, const char *func, const char *format) { + static char buffer[LINE_MAX]; + + snprintf(buffer, sizeof(buffer), format, text, file, line, func); + + char_array_0(buffer); + log_abort_msg = buffer; + + log_dispatch(LOG_CRIT, file, line, func, NULL, NULL, buffer); + abort(); +} +#pragma GCC diagnostic pop + +_noreturn_ void log_assert_failed(const char *text, const char *file, int line, const char *func) { + log_assert(text, file, line, func, "Assertion '%s' failed at %s:%u, function %s(). Aborting."); +} + +_noreturn_ void log_assert_failed_unreachable(const char *text, const char *file, int line, const char *func) { + log_assert(text, file, line, func, "Code should not be reached '%s' at %s:%u, function %s(). Aborting."); +} + +int log_oom_internal(const char *file, int line, const char *func) { + log_meta(LOG_ERR, file, line, func, "Out of memory."); + return -ENOMEM; +} + +int log_struct_internal( + int level, + const char *file, + int line, + const char *func, + const char *format, ...) { + + PROTECT_ERRNO; + va_list ap; + int r; + + if (_likely_(LOG_PRI(level) > log_max_level)) + return 0; + + if (log_target == LOG_TARGET_NULL) + return 0; + + if ((level & LOG_FACMASK) == 0) + level = log_facility | LOG_PRI(level); + + if ((log_target == LOG_TARGET_AUTO || + log_target == LOG_TARGET_JOURNAL_OR_KMSG || + log_target == LOG_TARGET_JOURNAL) && + journal_fd >= 0) { + + char header[LINE_MAX]; + struct iovec iovec[17] = {}; + unsigned n = 0, i; + struct msghdr mh = { + .msg_iov = iovec, + }; + static const char nl = '\n'; + + /* If the journal is available do structured logging */ + log_do_header(header, sizeof(header), level, + file, line, func, NULL, NULL); + IOVEC_SET_STRING(iovec[n++], header); + + va_start(ap, format); + while (format && n + 1 < ELEMENTSOF(iovec)) { + char *buf; + va_list aq; + + /* We need to copy the va_list structure, + * since vasprintf() leaves it afterwards at + * an undefined location */ + + va_copy(aq, ap); + if (vasprintf(&buf, format, aq) < 0) { + va_end(aq); + r = -ENOMEM; + goto finish; + } + va_end(aq); + + /* Now, jump enough ahead, so that we point to + * the next format string */ + VA_FORMAT_ADVANCE(format, ap); + + IOVEC_SET_STRING(iovec[n++], buf); + + iovec[n].iov_base = (char*) &nl; + iovec[n].iov_len = 1; + n++; + + format = va_arg(ap, char *); + } + + mh.msg_iovlen = n; + + if (sendmsg(journal_fd, &mh, MSG_NOSIGNAL) < 0) + r = -errno; + else + r = 1; + + finish: + va_end(ap); + for (i = 1; i < n; i += 2) + free(iovec[i].iov_base); + + } else { + char buf[LINE_MAX]; + bool found = false; + + /* Fallback if journal logging is not available */ + + va_start(ap, format); + while (format) { + va_list aq; + + va_copy(aq, ap); + vsnprintf(buf, sizeof(buf), format, aq); + va_end(aq); + char_array_0(buf); + + if (startswith(buf, "MESSAGE=")) { + found = true; + break; + } + + VA_FORMAT_ADVANCE(format, ap); + + format = va_arg(ap, char *); + } + va_end(ap); + + if (found) + r = log_dispatch(level, file, line, func, + NULL, NULL, buf + 8); + else + r = -EINVAL; + } + + return r; +} + +int log_set_target_from_string(const char *e) { + LogTarget t; + + t = log_target_from_string(e); + if (t < 0) + return -EINVAL; + + log_set_target(t); + return 0; +} + +int log_set_max_level_from_string(const char *e) { + int t; + + t = log_level_from_string(e); + if (t < 0) + return t; + + log_set_max_level(t); + return 0; +} + +void log_parse_environment(void) { + const char *e; + + e = secure_getenv("SYSTEMD_LOG_TARGET"); + if (e && log_set_target_from_string(e) < 0) + log_warning("Failed to parse log target %s. Ignoring.", e); + + e = secure_getenv("SYSTEMD_LOG_LEVEL"); + if (e && log_set_max_level_from_string(e) < 0) + log_warning("Failed to parse log level %s. Ignoring.", e); + + e = secure_getenv("SYSTEMD_LOG_COLOR"); + if (e && log_show_color_from_string(e) < 0) + log_warning("Failed to parse bool %s. Ignoring.", e); + + e = secure_getenv("SYSTEMD_LOG_LOCATION"); + if (e && log_show_location_from_string(e) < 0) + log_warning("Failed to parse bool %s. Ignoring.", e); +} + +LogTarget log_get_target(void) { + return log_target; +} + +int log_get_max_level(void) { + return log_max_level; +} + +void log_show_color(bool b) { + show_color = b; +} + +void log_show_location(bool b) { + show_location = b; +} + +int log_show_color_from_string(const char *e) { + int t; + + t = parse_boolean(e); + if (t < 0) + return t; + + log_show_color(t); + return 0; +} + +int log_show_location_from_string(const char *e) { + int t; + + t = parse_boolean(e); + if (t < 0) + return t; + + log_show_location(t); + return 0; +} + +bool log_on_console(void) { + if (log_target == LOG_TARGET_CONSOLE) + return true; + + return syslog_fd < 0 && kmsg_fd < 0 && journal_fd < 0; +} + +static const char *const log_target_table[] = { + [LOG_TARGET_CONSOLE] = "console", + [LOG_TARGET_KMSG] = "kmsg", + [LOG_TARGET_JOURNAL] = "journal", + [LOG_TARGET_JOURNAL_OR_KMSG] = "journal-or-kmsg", + [LOG_TARGET_SYSLOG] = "syslog", + [LOG_TARGET_SYSLOG_OR_KMSG] = "syslog-or-kmsg", + [LOG_TARGET_AUTO] = "auto", + [LOG_TARGET_SAFE] = "safe", + [LOG_TARGET_NULL] = "null" +}; + +DEFINE_STRING_TABLE_LOOKUP(log_target, LogTarget); diff --git a/src/shared/log.h b/src/shared/log.h new file mode 100644 index 0000000000..979f833d57 --- /dev/null +++ b/src/shared/log.h @@ -0,0 +1,159 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <syslog.h> +#include <stdbool.h> +#include <stdarg.h> +#include <errno.h> + +#include "macro.h" +#include "sd-id128.h" + +typedef enum LogTarget{ + LOG_TARGET_CONSOLE, + LOG_TARGET_KMSG, + LOG_TARGET_JOURNAL, + LOG_TARGET_JOURNAL_OR_KMSG, + LOG_TARGET_SYSLOG, + LOG_TARGET_SYSLOG_OR_KMSG, + LOG_TARGET_AUTO, /* console if stderr is tty, JOURNAL_OR_KMSG otherwise */ + LOG_TARGET_SAFE, /* console if stderr is tty, KMSG otherwise */ + LOG_TARGET_NULL, + _LOG_TARGET_MAX, + _LOG_TARGET_INVALID = -1 +} LogTarget; + +void log_set_target(LogTarget target); +void log_set_max_level(int level); +void log_set_facility(int facility); + +int log_set_target_from_string(const char *e); +int log_set_max_level_from_string(const char *e); + +void log_show_color(bool b); +void log_show_location(bool b); + +int log_show_color_from_string(const char *e); +int log_show_location_from_string(const char *e); + +LogTarget log_get_target(void) _pure_; +int log_get_max_level(void) _pure_; + +int log_open(void); +void log_close(void); +void log_forget_fds(void); + +void log_close_syslog(void); +void log_close_journal(void); +void log_close_kmsg(void); +void log_close_console(void); + +void log_parse_environment(void); + +int log_meta( + int level, + const char*file, + int line, + const char *func, + const char *format, ...) _printf_attr_(5,6); + +int log_metav( + int level, + const char*file, + int line, + const char *func, + const char *format, + va_list ap) _printf_attr_(5,0); + +int log_meta_object( + int level, + const char*file, + int line, + const char *func, + const char *object_name, + const char *object, + const char *format, ...) _printf_attr_(7,8); + +int log_metav_object( + int level, + const char*file, + int line, + const char *func, + const char *object_name, + const char *object, + const char *format, + va_list ap) _printf_attr_(7,0); + +int log_struct_internal( + int level, + const char *file, + int line, + const char *func, + const char *format, ...) _printf_attr_(5,0) _sentinel_; + +int log_oom_internal( + const char *file, + int line, + const char *func); + +/* This modifies the buffer passed! */ +int log_dump_internal( + int level, + const char*file, + int line, + const char *func, + char *buffer); + +_noreturn_ void log_assert_failed( + const char *text, + const char *file, + int line, + const char *func); + +_noreturn_ void log_assert_failed_unreachable( + const char *text, + const char *file, + int line, + const char *func); + +#define log_full(level, ...) log_meta(level, __FILE__, __LINE__, __func__, __VA_ARGS__) + +#define log_debug(...) log_meta(LOG_DEBUG, __FILE__, __LINE__, __func__, __VA_ARGS__) +#define log_info(...) log_meta(LOG_INFO, __FILE__, __LINE__, __func__, __VA_ARGS__) +#define log_notice(...) log_meta(LOG_NOTICE, __FILE__, __LINE__, __func__, __VA_ARGS__) +#define log_warning(...) log_meta(LOG_WARNING, __FILE__, __LINE__, __func__, __VA_ARGS__) +#define log_error(...) log_meta(LOG_ERR, __FILE__, __LINE__, __func__, __VA_ARGS__) + +#define log_struct(level, ...) log_struct_internal(level, __FILE__, __LINE__, __func__, __VA_ARGS__) + +#define log_oom() log_oom_internal(__FILE__, __LINE__, __func__) + +/* This modifies the buffer passed! */ +#define log_dump(level, buffer) log_dump_internal(level, __FILE__, __LINE__, __func__, buffer) + +bool log_on_console(void) _pure_; + +const char *log_target_to_string(LogTarget target) _const_; +LogTarget log_target_from_string(const char *s) _pure_; + +#define MESSAGE_ID(x) "MESSAGE_ID=" SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(x) diff --git a/src/shared/logs-show.c b/src/shared/logs-show.c new file mode 100644 index 0000000000..116dc8a36c --- /dev/null +++ b/src/shared/logs-show.c @@ -0,0 +1,985 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2012 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <time.h> +#include <assert.h> +#include <errno.h> +#include <sys/poll.h> +#include <string.h> + +#include "logs-show.h" +#include "log.h" +#include "util.h" +#include "utf8.h" +#include "hashmap.h" +#include "journal-internal.h" + +#define PRINT_THRESHOLD 128 +#define JSON_THRESHOLD 4096 + +static int print_catalog(FILE *f, sd_journal *j) { + int r; + _cleanup_free_ char *t = NULL, *z = NULL; + + + r = sd_journal_get_catalog(j, &t); + if (r < 0) + return r; + + z = strreplace(strstrip(t), "\n", "\n-- "); + if (!z) + return log_oom(); + + fputs("-- ", f); + fputs(z, f); + fputc('\n', f); + + return 0; +} + +static int parse_field(const void *data, size_t length, const char *field, char **target, size_t *target_size) { + size_t fl, nl; + void *buf; + + assert(data); + assert(field); + assert(target); + assert(target_size); + + fl = strlen(field); + if (length < fl) + return 0; + + if (memcmp(data, field, fl)) + return 0; + + nl = length - fl; + buf = malloc(nl+1); + if (!buf) + return log_oom(); + + memcpy(buf, (const char*) data + fl, nl); + ((char*)buf)[nl] = 0; + + free(*target); + *target = buf; + *target_size = nl; + + return 1; +} + +static bool shall_print(const char *p, size_t l, OutputFlags flags) { + assert(p); + + if (flags & OUTPUT_SHOW_ALL) + return true; + + if (l >= PRINT_THRESHOLD) + return false; + + if (!utf8_is_printable_n(p, l)) + return false; + + return true; +} + +static int output_short( + FILE *f, + sd_journal *j, + OutputMode mode, + unsigned n_columns, + OutputFlags flags) { + + int r; + const void *data; + size_t length; + size_t n = 0; + _cleanup_free_ char *hostname = NULL, *identifier = NULL, *comm = NULL, *pid = NULL, *fake_pid = NULL, *message = NULL, *realtime = NULL, *monotonic = NULL, *priority = NULL; + size_t hostname_len = 0, identifier_len = 0, comm_len = 0, pid_len = 0, fake_pid_len = 0, message_len = 0, realtime_len = 0, monotonic_len = 0, priority_len = 0; + int p = LOG_INFO; + const char *color_on = "", *color_off = ""; + + assert(f); + assert(j); + + sd_journal_set_data_threshold(j, flags & OUTPUT_SHOW_ALL ? 0 : PRINT_THRESHOLD); + + SD_JOURNAL_FOREACH_DATA(j, data, length) { + + r = parse_field(data, length, "PRIORITY=", &priority, &priority_len); + if (r < 0) + return r; + else if (r > 0) + continue; + + r = parse_field(data, length, "_HOSTNAME=", &hostname, &hostname_len); + if (r < 0) + return r; + else if (r > 0) + continue; + + r = parse_field(data, length, "SYSLOG_IDENTIFIER=", &identifier, &identifier_len); + if (r < 0) + return r; + else if (r > 0) + continue; + + r = parse_field(data, length, "_COMM=", &comm, &comm_len); + if (r < 0) + return r; + else if (r > 0) + continue; + + r = parse_field(data, length, "_PID=", &pid, &pid_len); + if (r < 0) + return r; + else if (r > 0) + continue; + + r = parse_field(data, length, "SYSLOG_PID=", &fake_pid, &fake_pid_len); + if (r < 0) + return r; + else if (r > 0) + continue; + + r = parse_field(data, length, "_SOURCE_REALTIME_TIMESTAMP=", &realtime, &realtime_len); + if (r < 0) + return r; + else if (r > 0) + continue; + + r = parse_field(data, length, "_SOURCE_MONOTONIC_TIMESTAMP=", &monotonic, &monotonic_len); + if (r < 0) + return r; + else if (r > 0) + continue; + + r = parse_field(data, length, "MESSAGE=", &message, &message_len); + if (r < 0) + return r; + } + + if (!message) + return 0; + + if (!(flags & OUTPUT_SHOW_ALL)) + strip_tab_ansi(&message, &message_len); + + if (priority_len == 1 && *priority >= '0' && *priority <= '7') + p = *priority - '0'; + + if (mode == OUTPUT_SHORT_MONOTONIC) { + uint64_t t; + sd_id128_t boot_id; + + r = -ENOENT; + + if (monotonic) + r = safe_atou64(monotonic, &t); + + if (r < 0) + r = sd_journal_get_monotonic_usec(j, &t, &boot_id); + + if (r < 0) { + log_error("Failed to get monotonic: %s", strerror(-r)); + return r; + } + + fprintf(f, "[%5llu.%06llu]", + (unsigned long long) (t / USEC_PER_SEC), + (unsigned long long) (t % USEC_PER_SEC)); + + n += 1 + 5 + 1 + 6 + 1; + + } else { + char buf[64]; + uint64_t x; + time_t t; + struct tm tm; + + r = -ENOENT; + + if (realtime) + r = safe_atou64(realtime, &x); + + if (r < 0) + r = sd_journal_get_realtime_usec(j, &x); + + if (r < 0) { + log_error("Failed to get realtime: %s", strerror(-r)); + return r; + } + + t = (time_t) (x / USEC_PER_SEC); + if (strftime(buf, sizeof(buf), "%b %d %H:%M:%S", localtime_r(&t, &tm)) <= 0) { + log_error("Failed to format time."); + return r; + } + + fputs(buf, f); + n += strlen(buf); + } + + if (hostname && shall_print(hostname, hostname_len, flags)) { + fprintf(f, " %.*s", (int) hostname_len, hostname); + n += hostname_len + 1; + } + + if (identifier && shall_print(identifier, identifier_len, flags)) { + fprintf(f, " %.*s", (int) identifier_len, identifier); + n += identifier_len + 1; + } else if (comm && shall_print(comm, comm_len, flags)) { + fprintf(f, " %.*s", (int) comm_len, comm); + n += comm_len + 1; + } else + fputc(' ', f); + + if (pid && shall_print(pid, pid_len, flags)) { + fprintf(f, "[%.*s]", (int) pid_len, pid); + n += pid_len + 2; + } else if (fake_pid && shall_print(fake_pid, fake_pid_len, flags)) { + fprintf(f, "[%.*s]", (int) fake_pid_len, fake_pid); + n += fake_pid_len + 2; + } + + if (flags & OUTPUT_COLOR) { + if (p <= LOG_ERR) { + color_on = ANSI_HIGHLIGHT_RED_ON; + color_off = ANSI_HIGHLIGHT_OFF; + } else if (p <= LOG_NOTICE) { + color_on = ANSI_HIGHLIGHT_ON; + color_off = ANSI_HIGHLIGHT_OFF; + } + } + + if (flags & OUTPUT_SHOW_ALL) + fprintf(f, ": %s%.*s%s\n", color_on, (int) message_len, message, color_off); + else if (!utf8_is_printable_n(message, message_len)) { + char bytes[FORMAT_BYTES_MAX]; + fprintf(f, ": [%s blob data]\n", format_bytes(bytes, sizeof(bytes), message_len)); + } else if ((flags & OUTPUT_FULL_WIDTH) || (message_len + n + 1 < n_columns)) + fprintf(f, ": %s%.*s%s\n", color_on, (int) message_len, message, color_off); + else if (n < n_columns && n_columns - n - 2 >= 3) { + _cleanup_free_ char *e; + + e = ellipsize_mem(message, message_len, n_columns - n - 2, 90); + + if (!e) + fprintf(f, ": %s%.*s%s\n", color_on, (int) message_len, message, color_off); + else + fprintf(f, ": %s%s%s\n", color_on, e, color_off); + } else + fputs("\n", f); + + if (flags & OUTPUT_CATALOG) + print_catalog(f, j); + + return 0; +} + +static int output_verbose( + FILE *f, + sd_journal *j, + OutputMode mode, + unsigned n_columns, + OutputFlags flags) { + + const void *data; + size_t length; + _cleanup_free_ char *cursor = NULL; + uint64_t realtime; + char ts[FORMAT_TIMESTAMP_MAX]; + int r; + + assert(f); + assert(j); + + sd_journal_set_data_threshold(j, 0); + + r = sd_journal_get_realtime_usec(j, &realtime); + if (r < 0) { + log_error("Failed to get realtime timestamp: %s", strerror(-r)); + return r; + } + + r = sd_journal_get_cursor(j, &cursor); + if (r < 0) { + log_error("Failed to get cursor: %s", strerror(-r)); + return r; + } + + fprintf(f, "%s [%s]\n", + format_timestamp(ts, sizeof(ts), realtime), + cursor); + + SD_JOURNAL_FOREACH_DATA(j, data, length) { + if (!shall_print(data, length, flags)) { + const char *c; + char bytes[FORMAT_BYTES_MAX]; + + c = memchr(data, '=', length); + if (!c) { + log_error("Invalid field."); + return -EINVAL; + } + + fprintf(f, "\t%.*s=[%s blob data]\n", + (int) (c - (const char*) data), + (const char*) data, + format_bytes(bytes, sizeof(bytes), length - (c - (const char *) data) - 1)); + } else + fprintf(f, "\t%.*s\n", (int) length, (const char*) data); + } + + if (flags & OUTPUT_CATALOG) + print_catalog(f, j); + + return 0; +} + +static int output_export( + FILE *f, + sd_journal *j, + OutputMode mode, + unsigned n_columns, + OutputFlags flags) { + + sd_id128_t boot_id; + char sid[33]; + int r; + usec_t realtime, monotonic; + _cleanup_free_ char *cursor = NULL; + const void *data; + size_t length; + + assert(j); + + sd_journal_set_data_threshold(j, 0); + + r = sd_journal_get_realtime_usec(j, &realtime); + if (r < 0) { + log_error("Failed to get realtime timestamp: %s", strerror(-r)); + return r; + } + + r = sd_journal_get_monotonic_usec(j, &monotonic, &boot_id); + if (r < 0) { + log_error("Failed to get monotonic timestamp: %s", strerror(-r)); + return r; + } + + r = sd_journal_get_cursor(j, &cursor); + if (r < 0) { + log_error("Failed to get cursor: %s", strerror(-r)); + return r; + } + + fprintf(f, + "__CURSOR=%s\n" + "__REALTIME_TIMESTAMP=%llu\n" + "__MONOTONIC_TIMESTAMP=%llu\n" + "_BOOT_ID=%s\n", + cursor, + (unsigned long long) realtime, + (unsigned long long) monotonic, + sd_id128_to_string(boot_id, sid)); + + SD_JOURNAL_FOREACH_DATA(j, data, length) { + + /* We already printed the boot id, from the data in + * the header, hence let's suppress it here */ + if (length >= 9 && + memcmp(data, "_BOOT_ID=", 9) == 0) + continue; + + if (!utf8_is_printable_n(data, length)) { + const char *c; + uint64_t le64; + + c = memchr(data, '=', length); + if (!c) { + log_error("Invalid field."); + return -EINVAL; + } + + fwrite(data, c - (const char*) data, 1, f); + fputc('\n', f); + le64 = htole64(length - (c - (const char*) data) - 1); + fwrite(&le64, sizeof(le64), 1, f); + fwrite(c + 1, length - (c - (const char*) data) - 1, 1, f); + } else + fwrite(data, length, 1, f); + + fputc('\n', f); + } + + fputc('\n', f); + + return 0; +} + +void json_escape( + FILE *f, + const char* p, + size_t l, + OutputFlags flags) { + + assert(f); + assert(p); + + if (!(flags & OUTPUT_SHOW_ALL) && l >= JSON_THRESHOLD) + + fputs("null", f); + + else if (!utf8_is_printable_n(p, l)) { + bool not_first = false; + + fputs("[ ", f); + + while (l > 0) { + if (not_first) + fprintf(f, ", %u", (uint8_t) *p); + else { + not_first = true; + fprintf(f, "%u", (uint8_t) *p); + } + + p++; + l--; + } + + fputs(" ]", f); + } else { + fputc('\"', f); + + while (l > 0) { + if (*p == '"' || *p == '\\') { + fputc('\\', f); + fputc(*p, f); + } else if (*p < ' ') + fprintf(f, "\\u%04x", *p); + else + fputc(*p, f); + + p++; + l--; + } + + fputc('\"', f); + } +} + +static int output_json( + FILE *f, + sd_journal *j, + OutputMode mode, + unsigned n_columns, + OutputFlags flags) { + + uint64_t realtime, monotonic; + _cleanup_free_ char *cursor = NULL; + const void *data; + size_t length; + sd_id128_t boot_id; + char sid[33], *k; + int r; + Hashmap *h = NULL; + bool done, separator; + + assert(j); + + sd_journal_set_data_threshold(j, flags & OUTPUT_SHOW_ALL ? 0 : JSON_THRESHOLD); + + r = sd_journal_get_realtime_usec(j, &realtime); + if (r < 0) { + log_error("Failed to get realtime timestamp: %s", strerror(-r)); + return r; + } + + r = sd_journal_get_monotonic_usec(j, &monotonic, &boot_id); + if (r < 0) { + log_error("Failed to get monotonic timestamp: %s", strerror(-r)); + return r; + } + + r = sd_journal_get_cursor(j, &cursor); + if (r < 0) { + log_error("Failed to get cursor: %s", strerror(-r)); + return r; + } + + if (mode == OUTPUT_JSON_PRETTY) + fprintf(f, + "{\n" + "\t\"__CURSOR\" : \"%s\",\n" + "\t\"__REALTIME_TIMESTAMP\" : \"%llu\",\n" + "\t\"__MONOTONIC_TIMESTAMP\" : \"%llu\",\n" + "\t\"_BOOT_ID\" : \"%s\"", + cursor, + (unsigned long long) realtime, + (unsigned long long) monotonic, + sd_id128_to_string(boot_id, sid)); + else { + if (mode == OUTPUT_JSON_SSE) + fputs("data: ", f); + + fprintf(f, + "{ \"__CURSOR\" : \"%s\", " + "\"__REALTIME_TIMESTAMP\" : \"%llu\", " + "\"__MONOTONIC_TIMESTAMP\" : \"%llu\", " + "\"_BOOT_ID\" : \"%s\"", + cursor, + (unsigned long long) realtime, + (unsigned long long) monotonic, + sd_id128_to_string(boot_id, sid)); + } + + h = hashmap_new(string_hash_func, string_compare_func); + if (!h) + return -ENOMEM; + + /* First round, iterate through the entry and count how often each field appears */ + SD_JOURNAL_FOREACH_DATA(j, data, length) { + const char *eq; + char *n; + unsigned u; + + if (length >= 9 && + memcmp(data, "_BOOT_ID=", 9) == 0) + continue; + + eq = memchr(data, '=', length); + if (!eq) + continue; + + n = strndup(data, eq - (const char*) data); + if (!n) { + r = -ENOMEM; + goto finish; + } + + u = PTR_TO_UINT(hashmap_get(h, n)); + if (u == 0) { + r = hashmap_put(h, n, UINT_TO_PTR(1)); + if (r < 0) { + free(n); + goto finish; + } + } else { + r = hashmap_update(h, n, UINT_TO_PTR(u + 1)); + free(n); + if (r < 0) + goto finish; + } + } + + separator = true; + do { + done = true; + + SD_JOURNAL_FOREACH_DATA(j, data, length) { + const char *eq; + char *kk, *n; + size_t m; + unsigned u; + + /* We already printed the boot id, from the data in + * the header, hence let's suppress it here */ + if (length >= 9 && + memcmp(data, "_BOOT_ID=", 9) == 0) + continue; + + eq = memchr(data, '=', length); + if (!eq) + continue; + + if (separator) { + if (mode == OUTPUT_JSON_PRETTY) + fputs(",\n\t", f); + else + fputs(", ", f); + } + + m = eq - (const char*) data; + + n = strndup(data, m); + if (!n) { + r = -ENOMEM; + goto finish; + } + + u = PTR_TO_UINT(hashmap_get2(h, n, (void**) &kk)); + if (u == 0) { + /* We already printed this, let's jump to the next */ + free(n); + separator = false; + + continue; + } else if (u == 1) { + /* Field only appears once, output it directly */ + + json_escape(f, data, m, flags); + fputs(" : ", f); + + json_escape(f, eq + 1, length - m - 1, flags); + + hashmap_remove(h, n); + free(kk); + free(n); + + separator = true; + + continue; + + } else { + /* Field appears multiple times, output it as array */ + json_escape(f, data, m, flags); + fputs(" : [ ", f); + json_escape(f, eq + 1, length - m - 1, flags); + + /* Iterate through the end of the list */ + + while (sd_journal_enumerate_data(j, &data, &length) > 0) { + if (length < m + 1) + continue; + + if (memcmp(data, n, m) != 0) + continue; + + if (((const char*) data)[m] != '=') + continue; + + fputs(", ", f); + json_escape(f, (const char*) data + m + 1, length - m - 1, flags); + } + + fputs(" ]", f); + + hashmap_remove(h, n); + free(kk); + free(n); + + /* Iterate data fields form the beginning */ + done = false; + separator = true; + + break; + } + } + + } while (!done); + + if (mode == OUTPUT_JSON_PRETTY) + fputs("\n}\n", f); + else if (mode == OUTPUT_JSON_SSE) + fputs("}\n\n", f); + else + fputs(" }\n", f); + + r = 0; + +finish: + while ((k = hashmap_steal_first_key(h))) + free(k); + + hashmap_free(h); + + return r; +} + +static int output_cat( + FILE *f, + sd_journal *j, + OutputMode mode, + unsigned n_columns, + OutputFlags flags) { + + const void *data; + size_t l; + int r; + + assert(j); + assert(f); + + sd_journal_set_data_threshold(j, 0); + + r = sd_journal_get_data(j, "MESSAGE", &data, &l); + if (r < 0) { + /* An entry without MESSAGE=? */ + if (r == -ENOENT) + return 0; + + log_error("Failed to get data: %s", strerror(-r)); + return r; + } + + assert(l >= 8); + + fwrite((const char*) data + 8, 1, l - 8, f); + fputc('\n', f); + + return 0; +} + +static int (*output_funcs[_OUTPUT_MODE_MAX])( + FILE *f, + sd_journal*j, + OutputMode mode, + unsigned n_columns, + OutputFlags flags) = { + + [OUTPUT_SHORT] = output_short, + [OUTPUT_SHORT_MONOTONIC] = output_short, + [OUTPUT_VERBOSE] = output_verbose, + [OUTPUT_EXPORT] = output_export, + [OUTPUT_JSON] = output_json, + [OUTPUT_JSON_PRETTY] = output_json, + [OUTPUT_JSON_SSE] = output_json, + [OUTPUT_CAT] = output_cat +}; + +int output_journal( + FILE *f, + sd_journal *j, + OutputMode mode, + unsigned n_columns, + OutputFlags flags) { + + int ret; + assert(mode >= 0); + assert(mode < _OUTPUT_MODE_MAX); + + if (n_columns <= 0) + n_columns = columns(); + + ret = output_funcs[mode](f, j, mode, n_columns, flags); + fflush(stdout); + return ret; +} + +static int show_journal(FILE *f, + sd_journal *j, + OutputMode mode, + unsigned n_columns, + usec_t not_before, + unsigned how_many, + OutputFlags flags) { + + int r; + unsigned line = 0; + bool need_seek = false; + int warn_cutoff = flags & OUTPUT_WARN_CUTOFF; + + assert(j); + assert(mode >= 0); + assert(mode < _OUTPUT_MODE_MAX); + + /* Seek to end */ + r = sd_journal_seek_tail(j); + if (r < 0) + goto finish; + + r = sd_journal_previous_skip(j, how_many); + if (r < 0) + goto finish; + + for (;;) { + for (;;) { + usec_t usec; + + if (need_seek) { + r = sd_journal_next(j); + if (r < 0) + goto finish; + } + + if (r == 0) + break; + + need_seek = true; + + if (not_before > 0) { + r = sd_journal_get_monotonic_usec(j, &usec, NULL); + + /* -ESTALE is returned if the + timestamp is not from this boot */ + if (r == -ESTALE) + continue; + else if (r < 0) + goto finish; + + if (usec < not_before) + continue; + } + + line ++; + + r = output_journal(f, j, mode, n_columns, flags); + if (r < 0) + goto finish; + } + + if (warn_cutoff && line < how_many && not_before > 0) { + sd_id128_t boot_id; + usec_t cutoff; + + /* Check whether the cutoff line is too early */ + + r = sd_id128_get_boot(&boot_id); + if (r < 0) + goto finish; + + r = sd_journal_get_cutoff_monotonic_usec(j, boot_id, &cutoff, NULL); + if (r < 0) + goto finish; + + if (r > 0 && not_before < cutoff) + fprintf(f, "Warning: Journal has been rotated since unit was started. Log output is incomplete or unavailable.\n"); + + warn_cutoff = false; + } + + if (!(flags & OUTPUT_FOLLOW)) + break; + + r = sd_journal_wait(j, (usec_t) -1); + if (r < 0) + goto finish; + + } + +finish: + return r; +} + +int add_matches_for_unit(sd_journal *j, const char *unit) { + int r; + _cleanup_free_ char *m1 = NULL, *m2 = NULL, *m3 = NULL; + + assert(j); + assert(unit); + + if (asprintf(&m1, "_SYSTEMD_UNIT=%s", unit) < 0 || + asprintf(&m2, "COREDUMP_UNIT=%s", unit) < 0 || + asprintf(&m3, "UNIT=%s", unit) < 0) + return -ENOMEM; + + (void)( + /* Look for messages from the service itself */ + (r = sd_journal_add_match(j, m1, 0)) || + + /* Look for coredumps of the service */ + (r = sd_journal_add_disjunction(j)) || + (r = sd_journal_add_match(j, + "MESSAGE_ID=fc2e22bc6ee647b6b90729ab34a250b1", 0)) || + (r = sd_journal_add_match(j, m2, 0)) || + + /* Look for messages from PID 1 about this service */ + (r = sd_journal_add_disjunction(j)) || + (r = sd_journal_add_match(j, "_PID=1", 0)) || + (r = sd_journal_add_match(j, m3, 0)) + ); + return r; +} + +int add_matches_for_user_unit(sd_journal *j, const char *unit, uid_t uid) { + int r; + _cleanup_free_ char *m1 = NULL, *m2 = NULL, *m3 = NULL, *m4 = NULL; + + assert(j); + assert(unit); + + if (asprintf(&m1, "_SYSTEMD_USER_UNIT=%s", unit) < 0 || + asprintf(&m2, "USER_UNIT=%s", unit) < 0 || + asprintf(&m3, "COREDUMP_USER_UNIT=%s", unit) < 0 || + asprintf(&m4, "_UID=%d", uid) < 0) + return -ENOMEM; + + (void) ( + /* Look for messages from the user service itself */ + (r = sd_journal_add_match(j, m1, 0)) || + (r = sd_journal_add_match(j, m4, 0)) || + + /* Look for messages from systemd about this service */ + (r = sd_journal_add_disjunction(j)) || + (r = sd_journal_add_match(j, m2, 0)) || + (r = sd_journal_add_match(j, m4, 0)) || + + /* Look for coredumps of the service */ + (r = sd_journal_add_disjunction(j)) || + (r = sd_journal_add_match(j, m3, 0)) || + (r = sd_journal_add_match(j, m4, 0)) + ); + return r; +} + +int show_journal_by_unit( + FILE *f, + const char *unit, + OutputMode mode, + unsigned n_columns, + usec_t not_before, + unsigned how_many, + uid_t uid, + OutputFlags flags, + bool system) { + + _cleanup_journal_close_ sd_journal*j = NULL; + int r; + int jflags = SD_JOURNAL_LOCAL_ONLY | system * SD_JOURNAL_SYSTEM_ONLY; + + assert(mode >= 0); + assert(mode < _OUTPUT_MODE_MAX); + assert(unit); + + if (how_many <= 0) + return 0; + + r = sd_journal_open(&j, jflags); + if (r < 0) + return r; + + if (system) + r = add_matches_for_unit(j, unit); + else + r = add_matches_for_user_unit(j, unit, uid); + if (r < 0) + return r; + + r = show_journal(f, j, mode, n_columns, not_before, how_many, flags); + if (r < 0) + return r; + + return 0; +} + +static const char *const output_mode_table[_OUTPUT_MODE_MAX] = { + [OUTPUT_SHORT] = "short", + [OUTPUT_SHORT_MONOTONIC] = "short-monotonic", + [OUTPUT_VERBOSE] = "verbose", + [OUTPUT_EXPORT] = "export", + [OUTPUT_JSON] = "json", + [OUTPUT_JSON_PRETTY] = "json-pretty", + [OUTPUT_JSON_SSE] = "json-sse", + [OUTPUT_CAT] = "cat" +}; + +DEFINE_STRING_TABLE_LOOKUP(output_mode, OutputMode); diff --git a/src/shared/logs-show.h b/src/shared/logs-show.h new file mode 100644 index 0000000000..b0f93a661a --- /dev/null +++ b/src/shared/logs-show.h @@ -0,0 +1,67 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2012 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <stdbool.h> +#include <unistd.h> +#include <sys/types.h> + +#include <systemd/sd-journal.h> + +#include "util.h" +#include "output-mode.h" + +int output_journal( + FILE *f, + sd_journal *j, + OutputMode mode, + unsigned n_columns, + OutputFlags flags); + +int add_matches_for_unit( + sd_journal *j, + const char *unit); + +int add_matches_for_user_unit( + sd_journal *j, + const char *unit, + uid_t uid); + +int show_journal_by_unit( + FILE *f, + const char *unit, + OutputMode mode, + unsigned n_columns, + usec_t not_before, + unsigned how_many, + uid_t uid, + OutputFlags flags, + bool system); + +void json_escape( + FILE *f, + const char* p, + size_t l, + OutputFlags flags); + +const char* output_mode_to_string(OutputMode m) _const_; +OutputMode output_mode_from_string(const char *s) _pure_; diff --git a/src/shared/macro.h b/src/shared/macro.h new file mode 100644 index 0000000000..0874102ece --- /dev/null +++ b/src/shared/macro.h @@ -0,0 +1,285 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <assert.h> +#include <sys/param.h> +#include <sys/types.h> +#include <sys/uio.h> +#include <inttypes.h> + +#define _printf_attr_(a,b) __attribute__ ((format (printf, a, b))) +#define _alloc_(...) __attribute__ ((alloc_size(__VA_ARGS__))) +#define _sentinel_ __attribute__ ((sentinel)) +#define _noreturn_ __attribute__((noreturn)) +#define _unused_ __attribute__ ((unused)) +#define _destructor_ __attribute__ ((destructor)) +#define _pure_ __attribute__ ((pure)) +#define _const_ __attribute__ ((const)) +#define _deprecated_ __attribute__ ((deprecated)) +#define _packed_ __attribute__ ((packed)) +#define _malloc_ __attribute__ ((malloc)) +#define _weak_ __attribute__ ((weak)) +#define _likely_(x) (__builtin_expect(!!(x),1)) +#define _unlikely_(x) (__builtin_expect(!!(x),0)) +#define _public_ __attribute__ ((visibility("default"))) +#define _hidden_ __attribute__ ((visibility("hidden"))) +#define _weakref_(x) __attribute__((weakref(#x))) +#define _introspect_(x) __attribute__((section("introspect." x))) +#define _alignas_(x) __attribute__((aligned(__alignof(x)))) +#define _cleanup_(x) __attribute__((cleanup(x))) + +/* automake test harness */ +#define EXIT_TEST_SKIP 77 + +#define XSTRINGIFY(x) #x +#define STRINGIFY(x) XSTRINGIFY(x) + +/* Rounds up */ + +#define ALIGN4(l) (((l) + 3) & ~3) +#define ALIGN8(l) (((l) + 7) & ~7) + +#if __SIZEOF_POINTER__ == 8 +#define ALIGN(l) ALIGN8(l) +#elif __SIZEOF_POINTER__ == 4 +#define ALIGN(l) ALIGN4(l) +#else +#error "Wut? Pointers are neither 4 nor 8 bytes long?" +#endif + +#define ALIGN_PTR(p) ((void*) ALIGN((unsigned long) p)) +#define ALIGN4_PTR(p) ((void*) ALIGN4((unsigned long) p)) +#define ALIGN8_PTR(p) ((void*) ALIGN8((unsigned long) p)) + +static inline size_t ALIGN_TO(size_t l, size_t ali) { + return ((l + ali - 1) & ~(ali - 1)); +} + +#define ALIGN_TO_PTR(p, ali) ((void*) ALIGN_TO((unsigned long) p)) + +#define ELEMENTSOF(x) (sizeof(x)/sizeof((x)[0])) + +/* + * container_of - cast a member of a structure out to the containing structure + * @ptr: the pointer to the member. + * @type: the type of the container struct this is embedded in. + * @member: the name of the member within the struct. + * + */ +#define container_of(ptr, type, member) \ + __extension__ ({ \ + const typeof( ((type *)0)->member ) *__mptr = (ptr); \ + (type *)( (char *)__mptr - offsetof(type,member) ); \ + }) + +#undef MAX +#define MAX(a,b) \ + __extension__ ({ \ + typeof(a) _a = (a); \ + typeof(b) _b = (b); \ + _a > _b ? _a : _b; \ + }) + +#define MAX3(x,y,z) \ + __extension__ ({ \ + typeof(x) _c = MAX(x,y); \ + MAX(_c, z); \ + }) + +#undef MIN +#define MIN(a,b) \ + __extension__ ({ \ + typeof(a) _a = (a); \ + typeof(b) _b = (b); \ + _a < _b ? _a : _b; \ + }) + +#ifndef CLAMP +#define CLAMP(x, low, high) \ + __extension__ ({ \ + typeof(x) _x = (x); \ + typeof(low) _low = (low); \ + typeof(high) _high = (high); \ + ((_x > _high) ? _high : ((_x < _low) ? _low : _x)); \ + }) +#endif + +#define assert_se(expr) \ + do { \ + if (_unlikely_(!(expr))) \ + log_assert_failed(#expr, __FILE__, __LINE__, __PRETTY_FUNCTION__); \ + } while (false) \ + +/* We override the glibc assert() here. */ +#undef assert +#ifdef NDEBUG +#define assert(expr) do {} while(false) +#else +#define assert(expr) assert_se(expr) +#endif + +#define assert_not_reached(t) \ + do { \ + log_assert_failed_unreachable(t, __FILE__, __LINE__, __PRETTY_FUNCTION__); \ + } while (false) + +#if defined(static_assert) +#define assert_cc(expr) \ + do { \ + static_assert(expr, #expr); \ + } while (false) +#else +#define assert_cc(expr) \ + do { \ + switch (0) { \ + case 0: \ + case !!(expr): \ + ; \ + } \ + } while (false) +#endif + +#define PTR_TO_UINT(p) ((unsigned int) ((uintptr_t) (p))) +#define UINT_TO_PTR(u) ((void*) ((uintptr_t) (u))) + +#define PTR_TO_UINT32(p) ((uint32_t) ((uintptr_t) (p))) +#define UINT32_TO_PTR(u) ((void*) ((uintptr_t) (u))) + +#define PTR_TO_ULONG(p) ((unsigned long) ((uintptr_t) (p))) +#define ULONG_TO_PTR(u) ((void*) ((uintptr_t) (u))) + +#define PTR_TO_INT(p) ((int) ((intptr_t) (p))) +#define INT_TO_PTR(u) ((void*) ((intptr_t) (u))) + +#define TO_INT32(p) ((int32_t) ((intptr_t) (p))) +#define INT32_TO_PTR(u) ((void*) ((intptr_t) (u))) + +#define PTR_TO_LONG(p) ((long) ((intptr_t) (p))) +#define LONG_TO_PTR(u) ((void*) ((intptr_t) (u))) + +#define memzero(x,l) (memset((x), 0, (l))) +#define zero(x) (memzero(&(x), sizeof(x))) + +#define CHAR_TO_STR(x) ((char[2]) { x, 0 }) + +#define char_array_0(x) x[sizeof(x)-1] = 0; + +#define IOVEC_SET_STRING(i, s) \ + do { \ + struct iovec *_i = &(i); \ + char *_s = (char *)(s); \ + _i->iov_base = _s; \ + _i->iov_len = strlen(_s); \ + } while(false) + +static inline size_t IOVEC_TOTAL_SIZE(const struct iovec *i, unsigned n) { + unsigned j; + size_t r = 0; + + for (j = 0; j < n; j++) + r += i[j].iov_len; + + return r; +} + +static inline size_t IOVEC_INCREMENT(struct iovec *i, unsigned n, size_t k) { + unsigned j; + + for (j = 0; j < n; j++) { + size_t sub; + + if (_unlikely_(k <= 0)) + break; + + sub = MIN(i[j].iov_len, k); + i[j].iov_len -= sub; + i[j].iov_base = (uint8_t*) i[j].iov_base + sub; + k -= sub; + } + + return k; +} + +#define VA_FORMAT_ADVANCE(format, ap) \ +do { \ + int _argtypes[128]; \ + size_t _i, _k; \ + _k = parse_printf_format((format), ELEMENTSOF(_argtypes), _argtypes); \ + assert(_k < ELEMENTSOF(_argtypes)); \ + for (_i = 0; _i < _k; _i++) { \ + if (_argtypes[_i] & PA_FLAG_PTR) { \ + (void) va_arg(ap, void*); \ + continue; \ + } \ + \ + switch (_argtypes[_i]) { \ + case PA_INT: \ + case PA_INT|PA_FLAG_SHORT: \ + case PA_CHAR: \ + (void) va_arg(ap, int); \ + break; \ + case PA_INT|PA_FLAG_LONG: \ + (void) va_arg(ap, long int); \ + break; \ + case PA_INT|PA_FLAG_LONG_LONG: \ + (void) va_arg(ap, long long int); \ + break; \ + case PA_WCHAR: \ + (void) va_arg(ap, wchar_t); \ + break; \ + case PA_WSTRING: \ + case PA_STRING: \ + case PA_POINTER: \ + (void) va_arg(ap, void*); \ + break; \ + case PA_FLOAT: \ + case PA_DOUBLE: \ + (void) va_arg(ap, double); \ + break; \ + case PA_DOUBLE|PA_FLAG_LONG_DOUBLE: \ + (void) va_arg(ap, long double); \ + break; \ + default: \ + assert_not_reached("Unknown format string argument."); \ + } \ + } \ +} while(false) + + /* Because statfs.t_type can be int on some architecures, we have to cast + * the const magic to the type, otherwise the compiler warns about + * signed/unsigned comparison, because the magic can be 32 bit unsigned. + */ +#define F_TYPE_CMP(a, b) (a == (typeof(a)) b) + + +/* Returns the number of chars needed to format variables of the + * specified type as a decimal string. Adds in extra space for a + * negative '-' prefix. */ + +#define DECIMAL_STR_MAX(type) \ + (1+(sizeof(type) <= 1 ? 3 : \ + sizeof(type) <= 2 ? 5 : \ + sizeof(type) <= 4 ? 10 : \ + sizeof(type) <= 8 ? 20 : sizeof(int[-2*(sizeof(type) > 8)]))) + +#include "log.h" diff --git a/src/shared/missing.h b/src/shared/missing.h new file mode 100644 index 0000000000..d4ba0d3dcf --- /dev/null +++ b/src/shared/missing.h @@ -0,0 +1,267 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +/* Missing glibc definitions to access certain kernel APIs */ + +#include <sys/resource.h> +#include <sys/syscall.h> +#include <fcntl.h> +#include <stdlib.h> +#include <unistd.h> +#include <linux/oom.h> + +#ifdef HAVE_AUDIT +#include <libaudit.h> +#endif + +#include "macro.h" + +#ifdef ARCH_MIPS +#include <asm/sgidefs.h> +#endif + +#ifndef RLIMIT_RTTIME +#define RLIMIT_RTTIME 15 +#endif + +#ifndef F_LINUX_SPECIFIC_BASE +#define F_LINUX_SPECIFIC_BASE 1024 +#endif + +#ifndef F_SETPIPE_SZ +#define F_SETPIPE_SZ (F_LINUX_SPECIFIC_BASE + 7) +#endif + +#ifndef F_GETPIPE_SZ +#define F_GETPIPE_SZ (F_LINUX_SPECIFIC_BASE + 8) +#endif + +#ifndef IP_FREEBIND +#define IP_FREEBIND 15 +#endif + +#ifndef OOM_SCORE_ADJ_MIN +#define OOM_SCORE_ADJ_MIN (-1000) +#endif + +#ifndef OOM_SCORE_ADJ_MAX +#define OOM_SCORE_ADJ_MAX 1000 +#endif + +#ifndef AUDIT_SERVICE_START +#define AUDIT_SERVICE_START 1130 /* Service (daemon) start */ +#endif + +#ifndef AUDIT_SERVICE_STOP +#define AUDIT_SERVICE_STOP 1131 /* Service (daemon) stop */ +#endif + +#ifndef TIOCVHANGUP +#define TIOCVHANGUP 0x5437 +#endif + +#ifndef IP_TRANSPARENT +#define IP_TRANSPARENT 19 +#endif + +#if !HAVE_DECL_PIVOT_ROOT +static inline int pivot_root(const char *new_root, const char *put_old) { + return syscall(SYS_pivot_root, new_root, put_old); +} +#endif + +#ifdef __x86_64__ +# ifndef __NR_fanotify_init +# define __NR_fanotify_init 300 +# endif +# ifndef __NR_fanotify_mark +# define __NR_fanotify_mark 301 +# endif +#elif defined _MIPS_SIM +# if _MIPS_SIM == _MIPS_SIM_ABI32 +# ifndef __NR_fanotify_init +# define __NR_fanotify_init 4336 +# endif +# ifndef __NR_fanotify_mark +# define __NR_fanotify_mark 4337 +# endif +# elif _MIPS_SIM == _MIPS_SIM_NABI32 +# ifndef __NR_fanotify_init +# define __NR_fanotify_init 6300 +# endif +# ifndef __NR_fanotify_mark +# define __NR_fanotify_mark 6301 +# endif +# elif _MIPS_SIM == _MIPS_SIM_ABI64 +# ifndef __NR_fanotify_init +# define __NR_fanotify_init 5295 +# endif +# ifndef __NR_fanotify_mark +# define __NR_fanotify_mark 5296 +# endif +# endif +#else +# ifndef __NR_fanotify_init +# define __NR_fanotify_init 338 +# endif +# ifndef __NR_fanotify_mark +# define __NR_fanotify_mark 339 +# endif +#endif + +#ifndef HAVE_FANOTIFY_INIT +static inline int fanotify_init(unsigned int flags, unsigned int event_f_flags) { + return syscall(__NR_fanotify_init, flags, event_f_flags); +} +#endif + +#ifndef HAVE_FANOTIFY_MARK +static inline int fanotify_mark(int fanotify_fd, unsigned int flags, uint64_t mask, + int dfd, const char *pathname) { +#if defined _MIPS_SIM && _MIPS_SIM == _MIPS_SIM_ABI32 || defined __powerpc__ && !defined __powerpc64__ + union { + uint64_t _64; + uint32_t _32[2]; + } _mask; + _mask._64 = mask; + + return syscall(__NR_fanotify_mark, fanotify_fd, flags, + _mask._32[0], _mask._32[1], dfd, pathname); +#else + return syscall(__NR_fanotify_mark, fanotify_fd, flags, mask, dfd, pathname); +#endif +} +#endif + +#ifndef BTRFS_IOCTL_MAGIC +#define BTRFS_IOCTL_MAGIC 0x94 +#endif + +#ifndef BTRFS_PATH_NAME_MAX +#define BTRFS_PATH_NAME_MAX 4087 +#endif + +struct btrfs_ioctl_vol_args { + int64_t fd; + char name[BTRFS_PATH_NAME_MAX + 1]; +}; + +#ifndef BTRFS_IOC_DEFRAG +#define BTRFS_IOC_DEFRAG _IOW(BTRFS_IOCTL_MAGIC, 2, struct btrfs_ioctl_vol_args) +#endif + +#ifndef BTRFS_SUPER_MAGIC +#define BTRFS_SUPER_MAGIC 0x9123683E +#endif + +#ifndef MS_MOVE +#define MS_MOVE 8192 +#endif + +#ifndef MS_PRIVATE +#define MS_PRIVATE (1 << 18) +#endif + +#if !HAVE_DECL_GETTID +static inline pid_t gettid(void) { + return (pid_t) syscall(SYS_gettid); +} +#endif + +#ifndef SCM_SECURITY +#define SCM_SECURITY 0x03 +#endif + +#ifndef MS_STRICTATIME +#define MS_STRICTATIME (1<<24) +#endif + +#ifndef MS_REC +#define MS_REC 16384 +#endif + +#ifndef MS_SHARED +#define MS_SHARED (1<<20) +#endif + +#ifndef PR_SET_NO_NEW_PRIVS +#define PR_SET_NO_NEW_PRIVS 38 +#endif + +#ifndef PR_SET_CHILD_SUBREAPER +#define PR_SET_CHILD_SUBREAPER 36 +#endif + +#ifndef MAX_HANDLE_SZ +#define MAX_HANDLE_SZ 128 +#endif + +#if defined __x86_64__ +# ifndef __NR_name_to_handle_at +# define __NR_name_to_handle_at 303 +# endif +#elif defined __i386__ +# ifndef __NR_name_to_handle_at +# define __NR_name_to_handle_at 341 +# endif +#elif defined __arm__ +# ifndef __NR_name_to_handle_at +# define __NR_name_to_handle_at 370 +# endif +#elif defined __powerpc__ +# ifndef __NR_name_to_handle_at +# define __NR_name_to_handle_at 345 +# endif +#else +# ifndef __NR_name_to_handle_at +# error __NR_name_to_handle_at is not defined +# endif +#endif + +#if !HAVE_DECL_NAME_TO_HANDLE_AT +struct file_handle { + unsigned int handle_bytes; + int handle_type; + unsigned char f_handle[0]; +}; + +static inline int name_to_handle_at(int fd, const char *name, struct file_handle *handle, int *mnt_id, int flags) { + return syscall(__NR_name_to_handle_at, fd, name, handle, mnt_id, flags); +} +#endif + +#ifndef HAVE_SECURE_GETENV +# ifdef HAVE___SECURE_GETENV +# define secure_getenv __secure_getenv +# else +# error neither secure_getenv nor __secure_getenv are available +# endif +#endif + +#ifndef CIFS_MAGIC_NUMBER +#define CIFS_MAGIC_NUMBER 0xFF534D42 +#endif + +#ifndef TFD_TIMER_CANCEL_ON_SET +#define TFD_TIMER_CANCEL_ON_SET (1 << 1) +#endif diff --git a/src/shared/mkdir.c b/src/shared/mkdir.c new file mode 100644 index 0000000000..0e51b64f69 --- /dev/null +++ b/src/shared/mkdir.c @@ -0,0 +1,147 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <assert.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <stdlib.h> +#include <stdio.h> + +#include "mkdir.h" +#include "label.h" +#include "util.h" + +int mkdir_label(const char *path, mode_t mode) { + return label_mkdir(path, mode, true); +} + +static int makedir_safe(const char *path, mode_t mode, uid_t uid, gid_t gid, bool apply) { + struct stat st; + + if (label_mkdir(path, mode, apply) >= 0) + if (chmod_and_chown(path, mode, uid, gid) < 0) + return -errno; + + if (lstat(path, &st) < 0) + return -errno; + + if ((st.st_mode & 0777) != mode || + st.st_uid != uid || + st.st_gid != gid || + !S_ISDIR(st.st_mode)) { + errno = EEXIST; + return -errno; + } + + return 0; +} + +int mkdir_safe(const char *path, mode_t mode, uid_t uid, gid_t gid) { + return makedir_safe(path, mode, uid, gid, false); +} + +int mkdir_safe_label(const char *path, mode_t mode, uid_t uid, gid_t gid) { + return makedir_safe(path, mode, uid, gid, true); +} + +static int makedir_parents(const char *path, mode_t mode, bool apply) { + struct stat st; + const char *p, *e; + + assert(path); + + /* return immediately if directory exists */ + e = strrchr(path, '/'); + if (!e) + return -EINVAL; + p = strndupa(path, e - path); + if (stat(p, &st) >= 0) { + if ((st.st_mode & S_IFMT) == S_IFDIR) + return 0; + else + return -ENOTDIR; + } + + /* create every parent directory in the path, except the last component */ + p = path + strspn(path, "/"); + for (;;) { + int r; + char *t; + + e = p + strcspn(p, "/"); + p = e + strspn(e, "/"); + + /* Is this the last component? If so, then we're + * done */ + if (*p == 0) + return 0; + + t = strndup(path, e - path); + if (!t) + return -ENOMEM; + + r = label_mkdir(t, mode, apply); + free(t); + + if (r < 0 && errno != EEXIST) + return -errno; + } +} + +int mkdir_parents(const char *path, mode_t mode) { + return makedir_parents(path, mode, false); +} + +int mkdir_parents_label(const char *path, mode_t mode) { + return makedir_parents(path, mode, true); +} + +static int is_dir(const char* path) { + struct stat st; + if (stat(path, &st) < 0) + return -errno; + return S_ISDIR(st.st_mode); +} + +static int makedir_p(const char *path, mode_t mode, bool apply) { + int r; + + /* Like mkdir -p */ + + r = makedir_parents(path, mode, apply); + if (r < 0) + return r; + + r = label_mkdir(path, mode, apply); + if (r < 0 && (errno != EEXIST || is_dir(path) <= 0)) + return -errno; + + return 0; +} + +int mkdir_p(const char *path, mode_t mode) { + return makedir_p(path, mode, false); +} + +int mkdir_p_label(const char *path, mode_t mode) { + return makedir_p(path, mode, true); +} diff --git a/src/shared/mkdir.h b/src/shared/mkdir.h new file mode 100644 index 0000000000..ce1c35e9ba --- /dev/null +++ b/src/shared/mkdir.h @@ -0,0 +1,32 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foomkdirhfoo +#define foomkdirhfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +int mkdir_label(const char *path, mode_t mode); +int mkdir_safe(const char *path, mode_t mode, uid_t uid, gid_t gid); +int mkdir_safe_label(const char *path, mode_t mode, uid_t uid, gid_t gid); +int mkdir_parents(const char *path, mode_t mode); +int mkdir_parents_label(const char *path, mode_t mode); +int mkdir_p(const char *path, mode_t mode); +int mkdir_p_label(const char *path, mode_t mode); +#endif diff --git a/src/shared/output-mode.h b/src/shared/output-mode.h new file mode 100644 index 0000000000..0efd430c5d --- /dev/null +++ b/src/shared/output-mode.h @@ -0,0 +1,44 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +typedef enum OutputMode { + OUTPUT_SHORT, + OUTPUT_SHORT_MONOTONIC, + OUTPUT_VERBOSE, + OUTPUT_EXPORT, + OUTPUT_JSON, + OUTPUT_JSON_PRETTY, + OUTPUT_JSON_SSE, + OUTPUT_CAT, + _OUTPUT_MODE_MAX, + _OUTPUT_MODE_INVALID = -1 +} OutputMode; + +typedef enum OutputFlags { + OUTPUT_SHOW_ALL = 1 << 0, + OUTPUT_FOLLOW = 1 << 1, + OUTPUT_WARN_CUTOFF = 1 << 2, + OUTPUT_FULL_WIDTH = 1 << 3, + OUTPUT_COLOR = 1 << 4, + OUTPUT_CATALOG = 1 << 5 +} OutputFlags; diff --git a/src/shared/pager.c b/src/shared/pager.c new file mode 100644 index 0000000000..8dddf24f4c --- /dev/null +++ b/src/shared/pager.c @@ -0,0 +1,146 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <sys/types.h> +#include <fcntl.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <sys/prctl.h> + +#include "pager.h" +#include "util.h" +#include "macro.h" + +static pid_t pager_pid = 0; + +_noreturn_ static void pager_fallback(void) { + ssize_t n; + do { + n = splice(STDIN_FILENO, NULL, STDOUT_FILENO, NULL, 64*1024, 0); + } while (n > 0); + if (n < 0) { + log_error("Internal pager failed: %m"); + _exit(EXIT_FAILURE); + } + _exit(EXIT_SUCCESS); +} + +int pager_open(bool jump_to_end) { + int fd[2]; + const char *pager; + pid_t parent_pid; + int r; + + if (pager_pid > 0) + return 1; + + if ((pager = getenv("SYSTEMD_PAGER")) || (pager = getenv("PAGER"))) + if (!*pager || streq(pager, "cat")) + return 0; + + if (!on_tty()) + return 0; + + /* Determine and cache number of columns before we spawn the + * pager so that we get the value from the actual tty */ + columns(); + + if (pipe(fd) < 0) { + log_error("Failed to create pager pipe: %m"); + return -errno; + } + + parent_pid = getpid(); + + pager_pid = fork(); + if (pager_pid < 0) { + r = -errno; + log_error("Failed to fork pager: %m"); + close_pipe(fd); + return r; + } + + /* In the child start the pager */ + if (pager_pid == 0) { + + dup2(fd[0], STDIN_FILENO); + close_pipe(fd); + + if (jump_to_end) + setenv("LESS", "FRSXMK+G", 1); + else + setenv("LESS", "FRSXMK", 1); + + /* Make sure the pager goes away when the parent dies */ + if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0) + _exit(EXIT_FAILURE); + + /* Check whether our parent died before we were able + * to set the death signal */ + if (getppid() != parent_pid) + _exit(EXIT_SUCCESS); + + if (pager) { + execlp(pager, pager, NULL); + execl("/bin/sh", "sh", "-c", pager, NULL); + } + + /* Debian's alternatives command for pagers is + * called 'pager'. Note that we do not call + * sensible-pagers here, since that is just a + * shell script that implements a logic that + * is similar to this one anyway, but is + * Debian-specific. */ + execlp("pager", "pager", NULL); + + execlp("less", "less", NULL); + execlp("more", "more", NULL); + + pager_fallback(); + /* not reached */ + } + + /* Return in the parent */ + if (dup2(fd[1], STDOUT_FILENO) < 0) { + log_error("Failed to duplicate pager pipe: %m"); + return -errno; + } + + close_pipe(fd); + return 1; +} + +void pager_close(void) { + + if (pager_pid <= 0) + return; + + /* Inform pager that we are done */ + fclose(stdout); + kill(pager_pid, SIGCONT); + wait_for_terminate(pager_pid, NULL); + pager_pid = 0; +} + +bool pager_have(void) { + return pager_pid > 0; +} diff --git a/src/shared/pager.h b/src/shared/pager.h new file mode 100644 index 0000000000..03dca8bc03 --- /dev/null +++ b/src/shared/pager.h @@ -0,0 +1,30 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <stdbool.h> + +#include "macro.h" + +int pager_open(bool jump_to_end); +void pager_close(void); +bool pager_have(void) _pure_; diff --git a/src/shared/path-lookup.c b/src/shared/path-lookup.c new file mode 100644 index 0000000000..1a47ea9ce7 --- /dev/null +++ b/src/shared/path-lookup.c @@ -0,0 +1,426 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <assert.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> + +#include "util.h" +#include "mkdir.h" +#include "strv.h" +#include "path-util.h" +#include "path-lookup.h" + +static const char* const systemd_running_as_table[_SYSTEMD_RUNNING_AS_MAX] = { + [SYSTEMD_SYSTEM] = "system", + [SYSTEMD_USER] = "user" +}; + +DEFINE_STRING_TABLE_LOOKUP(systemd_running_as, SystemdRunningAs); + +int user_config_home(char **config_home) { + const char *e; + char *r; + + e = getenv("XDG_CONFIG_HOME"); + if (e) { + r = strappend(e, "/systemd/user"); + if (!r) + return -ENOMEM; + + *config_home = r; + return 1; + } else { + const char *home; + + home = getenv("HOME"); + if (home) { + r = strappend(home, "/.config/systemd/user"); + if (!r) + return -ENOMEM; + + *config_home = r; + return 1; + } + } + + return 0; +} + +static char** user_dirs( + const char *generator, + const char *generator_early, + const char *generator_late) { + + const char * const config_unit_paths[] = { + USER_CONFIG_UNIT_PATH, + "/etc/systemd/user", + "/run/systemd/user", + NULL + }; + + const char * const data_unit_paths[] = { + "/usr/local/lib/systemd/user", + "/usr/local/share/systemd/user", + USER_DATA_UNIT_PATH, + "/usr/lib/systemd/user", + "/usr/share/systemd/user", + NULL + }; + + const char *home, *e; + char *config_home = NULL, *data_home = NULL; + char **config_dirs = NULL, **data_dirs = NULL; + char **r = NULL, **t; + + /* Implement the mechanisms defined in + * + * http://standards.freedesktop.org/basedir-spec/basedir-spec-0.6.html + * + * We look in both the config and the data dirs because we + * want to encourage that distributors ship their unit files + * as data, and allow overriding as configuration. + */ + + if (user_config_home(&config_home) < 0) + goto fail; + + home = getenv("HOME"); + + e = getenv("XDG_CONFIG_DIRS"); + if (e) { + config_dirs = strv_split(e, ":"); + if (!config_dirs) + goto fail; + } + + /* We don't treat /etc/xdg/systemd here as the spec + * suggests because we assume that that is a link to + * /etc/systemd/ anyway. */ + + e = getenv("XDG_DATA_HOME"); + if (e) { + if (asprintf(&data_home, "%s/systemd/user", e) < 0) + goto fail; + + } else if (home) { + if (asprintf(&data_home, "%s/.local/share/systemd/user", home) < 0) + goto fail; + + /* There is really no need for two unit dirs in $HOME, + * except to be fully compliant with the XDG spec. We + * now try to link the two dirs, so that we can + * minimize disk seeks a little. Further down we'll + * then filter out this link, if it is actually is + * one. */ + + mkdir_parents_label(data_home, 0777); + (void) symlink("../../../.config/systemd/user", data_home); + } + + e = getenv("XDG_DATA_DIRS"); + if (e) + data_dirs = strv_split(e, ":"); + else + data_dirs = strv_new("/usr/local/share", + "/usr/share", + NULL); + if (!data_dirs) + goto fail; + + /* Now merge everything we found. */ + if (generator_early) { + t = strv_append(r, generator_early); + if (!t) + goto fail; + strv_free(r); + r = t; + } + + if (config_home) { + t = strv_append(r, config_home); + if (!t) + goto fail; + strv_free(r); + r = t; + } + + if (!strv_isempty(config_dirs)) { + t = strv_merge_concat(r, config_dirs, "/systemd/user"); + if (!t) + goto finish; + strv_free(r); + r = t; + } + + t = strv_merge(r, (char**) config_unit_paths); + if (!t) + goto fail; + strv_free(r); + r = t; + + if (generator) { + t = strv_append(r, generator); + if (!t) + goto fail; + strv_free(r); + r = t; + } + + if (data_home) { + t = strv_append(r, data_home); + if (!t) + goto fail; + strv_free(r); + r = t; + } + + if (!strv_isempty(data_dirs)) { + t = strv_merge_concat(r, data_dirs, "/systemd/user"); + if (!t) + goto fail; + strv_free(r); + r = t; + } + + t = strv_merge(r, (char**) data_unit_paths); + if (!t) + goto fail; + strv_free(r); + r = t; + + if (generator_late) { + t = strv_append(r, generator_late); + if (!t) + goto fail; + strv_free(r); + r = t; + } + + if (!path_strv_make_absolute_cwd(r)) + goto fail; + +finish: + free(config_home); + strv_free(config_dirs); + free(data_home); + strv_free(data_dirs); + + return r; + +fail: + strv_free(r); + r = NULL; + goto finish; +} + +int lookup_paths_init( + LookupPaths *p, + SystemdRunningAs running_as, + bool personal, + const char *generator, + const char *generator_early, + const char *generator_late) { + + const char *e; + + assert(p); + + /* First priority is whatever has been passed to us via env + * vars */ + e = getenv("SYSTEMD_UNIT_PATH"); + if (e) { + p->unit_path = path_split_and_make_absolute(e); + if (!p->unit_path) + return -ENOMEM; + } else + p->unit_path = NULL; + + if (strv_isempty(p->unit_path)) { + /* Nothing is set, so let's figure something out. */ + strv_free(p->unit_path); + + /* For the user units we include share/ in the search + * path in order to comply with the XDG basedir + * spec. For the system stuff we avoid such + * nonsense. OTOH we include /lib in the search path + * for the system stuff but avoid it for user + * stuff. */ + + if (running_as == SYSTEMD_USER) { + + if (personal) + p->unit_path = user_dirs(generator, generator_early, generator_late); + else + p->unit_path = strv_new( + /* If you modify this you also want to modify + * systemduserunitpath= in systemd.pc.in, and + * the arrays in user_dirs() above! */ + STRV_IFNOTNULL(generator_early), + USER_CONFIG_UNIT_PATH, + "/etc/systemd/user", + "/run/systemd/user", + STRV_IFNOTNULL(generator), + "/usr/local/lib/systemd/user", + "/usr/local/share/systemd/user", + USER_DATA_UNIT_PATH, + "/usr/lib/systemd/user", + "/usr/share/systemd/user", + STRV_IFNOTNULL(generator_late), + NULL); + + if (!p->unit_path) + return -ENOMEM; + + } else { + p->unit_path = strv_new( + /* If you modify this you also want to modify + * systemdsystemunitpath= in systemd.pc.in! */ + STRV_IFNOTNULL(generator_early), + SYSTEM_CONFIG_UNIT_PATH, + "/etc/systemd/system", + "/run/systemd/system", + STRV_IFNOTNULL(generator), + "/usr/local/lib/systemd/system", + SYSTEM_DATA_UNIT_PATH, + "/usr/lib/systemd/system", +#ifdef HAVE_SPLIT_USR + "/lib/systemd/system", +#endif + STRV_IFNOTNULL(generator_late), + NULL); + + if (!p->unit_path) + return -ENOMEM; + } + } + + if (!path_strv_canonicalize(p->unit_path)) + return -ENOMEM; + + strv_uniq(p->unit_path); + + if (!strv_isempty(p->unit_path)) { + _cleanup_free_ char *t = strv_join(p->unit_path, "\n\t"); + if (!t) + return -ENOMEM; + log_debug("Looking for unit files in (higher priority first):\n\t%s", t); + } else { + log_debug("Ignoring unit files."); + strv_free(p->unit_path); + p->unit_path = NULL; + } + + if (running_as == SYSTEMD_SYSTEM) { +#ifdef HAVE_SYSV_COMPAT + /* /etc/init.d/ compatibility does not matter to users */ + + e = getenv("SYSTEMD_SYSVINIT_PATH"); + if (e) { + p->sysvinit_path = path_split_and_make_absolute(e); + if (!p->sysvinit_path) + return -ENOMEM; + } else + p->sysvinit_path = NULL; + + if (strv_isempty(p->sysvinit_path)) { + strv_free(p->sysvinit_path); + + p->sysvinit_path = strv_new( + SYSTEM_SYSVINIT_PATH, /* /etc/init.d/ */ + NULL); + if (!p->sysvinit_path) + return -ENOMEM; + } + + e = getenv("SYSTEMD_SYSVRCND_PATH"); + if (e) { + p->sysvrcnd_path = path_split_and_make_absolute(e); + if (!p->sysvrcnd_path) + return -ENOMEM; + } else + p->sysvrcnd_path = NULL; + + if (strv_isempty(p->sysvrcnd_path)) { + strv_free(p->sysvrcnd_path); + + p->sysvrcnd_path = strv_new( + SYSTEM_SYSVRCND_PATH, /* /etc/rcN.d/ */ + NULL); + if (!p->sysvrcnd_path) + return -ENOMEM; + } + + if (!path_strv_canonicalize(p->sysvinit_path)) + return -ENOMEM; + + if (!path_strv_canonicalize(p->sysvrcnd_path)) + return -ENOMEM; + + strv_uniq(p->sysvinit_path); + strv_uniq(p->sysvrcnd_path); + + if (!strv_isempty(p->sysvinit_path)) { + _cleanup_free_ char *t = strv_join(p->sysvinit_path, "\n\t"); + if (!t) + return -ENOMEM; + log_debug("Looking for SysV init scripts in:\n\t%s", t); + } else { + log_debug("Ignoring SysV init scripts."); + strv_free(p->sysvinit_path); + p->sysvinit_path = NULL; + } + + if (!strv_isempty(p->sysvrcnd_path)) { + _cleanup_free_ char *t = + strv_join(p->sysvrcnd_path, "\n\t"); + if (!t) + return -ENOMEM; + + log_debug("Looking for SysV rcN.d links in:\n\t%s", t); + } else { + log_debug("Ignoring SysV rcN.d links."); + strv_free(p->sysvrcnd_path); + p->sysvrcnd_path = NULL; + } +#else + log_debug("SysV init scripts and rcN.d links support disabled"); +#endif + } + + return 0; +} + +void lookup_paths_free(LookupPaths *p) { + assert(p); + + strv_free(p->unit_path); + p->unit_path = NULL; + +#ifdef HAVE_SYSV_COMPAT + strv_free(p->sysvinit_path); + strv_free(p->sysvrcnd_path); + p->sysvinit_path = p->sysvrcnd_path = NULL; +#endif +} diff --git a/src/shared/path-lookup.h b/src/shared/path-lookup.h new file mode 100644 index 0000000000..9dee85f967 --- /dev/null +++ b/src/shared/path-lookup.h @@ -0,0 +1,45 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +typedef struct LookupPaths { + char **unit_path; +#ifdef HAVE_SYSV_COMPAT + char **sysvinit_path; + char **sysvrcnd_path; +#endif +} LookupPaths; + +typedef enum SystemdRunningAs { + SYSTEMD_SYSTEM, + SYSTEMD_USER, + _SYSTEMD_RUNNING_AS_MAX, + _SYSTEMD_RUNNING_AS_INVALID = -1 +} SystemdRunningAs; + +const char* systemd_running_as_to_string(SystemdRunningAs i) _const_; +SystemdRunningAs systemd_running_as_from_string(const char *s) _pure_; + +int user_config_home(char **config_home); + +int lookup_paths_init(LookupPaths *p, SystemdRunningAs running_as, bool personal, const char *generator, const char *generator_early, const char *generator_late); +void lookup_paths_free(LookupPaths *p); diff --git a/src/shared/path-util.c b/src/shared/path-util.c new file mode 100644 index 0000000000..0c1b6a0ab0 --- /dev/null +++ b/src/shared/path-util.c @@ -0,0 +1,428 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010-2012 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <assert.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <stdlib.h> +#include <signal.h> +#include <stdio.h> +#include <fcntl.h> +#include <dirent.h> +#include <sys/statvfs.h> + +#include "macro.h" +#include "util.h" +#include "log.h" +#include "strv.h" +#include "path-util.h" +#include "missing.h" + +bool path_is_absolute(const char *p) { + return p[0] == '/'; +} + +bool is_path(const char *p) { + return !!strchr(p, '/'); +} + +char *path_get_file_name(const char *p) { + char *r; + + assert(p); + + r = strrchr(p, '/'); + if (r) + return r + 1; + + return (char*) p; +} + +int path_get_parent(const char *path, char **_r) { + const char *e, *a = NULL, *b = NULL, *p; + char *r; + bool slash = false; + + assert(path); + assert(_r); + + if (!*path) + return -EINVAL; + + for (e = path; *e; e++) { + + if (!slash && *e == '/') { + a = b; + b = e; + slash = true; + } else if (slash && *e != '/') + slash = false; + } + + if (*(e-1) == '/') + p = a; + else + p = b; + + if (!p) + return -EINVAL; + + if (p == path) + r = strdup("/"); + else + r = strndup(path, p-path); + + if (!r) + return -ENOMEM; + + *_r = r; + return 0; +} + +char **path_split_and_make_absolute(const char *p) { + char **l; + assert(p); + + if (!(l = strv_split(p, ":"))) + return NULL; + + if (!path_strv_make_absolute_cwd(l)) { + strv_free(l); + return NULL; + } + + return l; +} + +char *path_make_absolute(const char *p, const char *prefix) { + assert(p); + + /* Makes every item in the list an absolute path by prepending + * the prefix, if specified and necessary */ + + if (path_is_absolute(p) || !prefix) + return strdup(p); + + return strjoin(prefix, "/", p, NULL); +} + +char *path_make_absolute_cwd(const char *p) { + char *cwd, *r; + + assert(p); + + /* Similar to path_make_absolute(), but prefixes with the + * current working directory. */ + + if (path_is_absolute(p)) + return strdup(p); + + cwd = get_current_dir_name(); + if (!cwd) + return NULL; + + r = path_make_absolute(p, cwd); + free(cwd); + + return r; +} + +char **path_strv_make_absolute_cwd(char **l) { + char **s; + + /* Goes through every item in the string list and makes it + * absolute. This works in place and won't rollback any + * changes on failure. */ + + STRV_FOREACH(s, l) { + char *t; + + if (!(t = path_make_absolute_cwd(*s))) + return NULL; + + free(*s); + *s = t; + } + + return l; +} + +char **path_strv_canonicalize(char **l) { + char **s; + unsigned k = 0; + bool enomem = false; + + if (strv_isempty(l)) + return l; + + /* Goes through every item in the string list and canonicalize + * the path. This works in place and won't rollback any + * changes on failure. */ + + STRV_FOREACH(s, l) { + char *t, *u; + + t = path_make_absolute_cwd(*s); + free(*s); + *s = NULL; + + if (!t) { + enomem = true; + continue; + } + + errno = 0; + u = canonicalize_file_name(t); + if (!u) { + if (errno == ENOENT) + u = t; + else { + free(t); + if (errno == ENOMEM || !errno) + enomem = true; + + continue; + } + } else + free(t); + + l[k++] = u; + } + + l[k] = NULL; + + if (enomem) + return NULL; + + return l; +} + +char **path_strv_canonicalize_uniq(char **l) { + if (strv_isempty(l)) + return l; + + if (!path_strv_canonicalize(l)) + return NULL; + + return strv_uniq(l); +} + +char *path_kill_slashes(char *path) { + char *f, *t; + bool slash = false; + + /* Removes redundant inner and trailing slashes. Modifies the + * passed string in-place. + * + * ///foo///bar/ becomes /foo/bar + */ + + for (f = path, t = path; *f; f++) { + + if (*f == '/') { + slash = true; + continue; + } + + if (slash) { + slash = false; + *(t++) = '/'; + } + + *(t++) = *f; + } + + /* Special rule, if we are talking of the root directory, a + trailing slash is good */ + + if (t == path && slash) + *(t++) = '/'; + + *t = 0; + return path; +} + +char* path_startswith(const char *path, const char *prefix) { + assert(path); + assert(prefix); + + if ((path[0] == '/') != (prefix[0] == '/')) + return NULL; + + for (;;) { + size_t a, b; + + path += strspn(path, "/"); + prefix += strspn(prefix, "/"); + + if (*prefix == 0) + return (char*) path; + + if (*path == 0) + return NULL; + + a = strcspn(path, "/"); + b = strcspn(prefix, "/"); + + if (a != b) + return NULL; + + if (memcmp(path, prefix, a) != 0) + return NULL; + + path += a; + prefix += b; + } +} + +bool path_equal(const char *a, const char *b) { + assert(a); + assert(b); + + if ((a[0] == '/') != (b[0] == '/')) + return false; + + for (;;) { + size_t j, k; + + a += strspn(a, "/"); + b += strspn(b, "/"); + + if (*a == 0 && *b == 0) + return true; + + if (*a == 0 || *b == 0) + return false; + + j = strcspn(a, "/"); + k = strcspn(b, "/"); + + if (j != k) + return false; + + if (memcmp(a, b, j) != 0) + return false; + + a += j; + b += k; + } +} + +int path_is_mount_point(const char *t, bool allow_symlink) { + char *parent; + int r; + struct file_handle *h; + int mount_id, mount_id_parent; + struct stat a, b; + + /* We are not actually interested in the file handles, but + * name_to_handle_at() also passes us the mount ID, hence use + * it but throw the handle away */ + + if (path_equal(t, "/")) + return 1; + + h = alloca(MAX_HANDLE_SZ); + h->handle_bytes = MAX_HANDLE_SZ; + + r = name_to_handle_at(AT_FDCWD, t, h, &mount_id, allow_symlink ? AT_SYMLINK_FOLLOW : 0); + if (r < 0) { + if (errno == ENOSYS || errno == ENOTSUP) + /* This kernel or file system does not support + * name_to_handle_at(), hence fallback to the + * traditional stat() logic */ + goto fallback; + + if (errno == ENOENT) + return 0; + + return -errno; + } + + r = path_get_parent(t, &parent); + if (r < 0) + return r; + + h->handle_bytes = MAX_HANDLE_SZ; + r = name_to_handle_at(AT_FDCWD, parent, h, &mount_id_parent, 0); + free(parent); + + if (r < 0) { + /* The parent can't do name_to_handle_at() but the + * directory we are interested in can? If so, it must + * be a mount point */ + if (errno == ENOTSUP) + return 1; + + return -errno; + } + + return mount_id != mount_id_parent; + +fallback: + if (allow_symlink) + r = stat(t, &a); + else + r = lstat(t, &a); + + if (r < 0) { + if (errno == ENOENT) + return 0; + + return -errno; + } + + r = path_get_parent(t, &parent); + if (r < 0) + return r; + + r = lstat(parent, &b); + free(parent); + + if (r < 0) + return -errno; + + return a.st_dev != b.st_dev; +} + +int path_is_read_only_fs(const char *path) { + struct statvfs st; + + assert(path); + + if (statvfs(path, &st) < 0) + return -errno; + + return !!(st.f_flag & ST_RDONLY); +} + +int path_is_os_tree(const char *path) { + char *p; + int r; + + /* We use /etc/os-release as flag file if something is an OS */ + + p = strappenda(path, "/etc/os-release"); + r = access(p, F_OK); + + return r < 0 ? 0 : 1; +} diff --git a/src/shared/path-util.h b/src/shared/path-util.h new file mode 100644 index 0000000000..d187743769 --- /dev/null +++ b/src/shared/path-util.h @@ -0,0 +1,45 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010-2012 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <stdbool.h> + +#include "macro.h" + +bool is_path(const char *p) _pure_; +char** path_split_and_make_absolute(const char *p); +char* path_get_file_name(const char *p) _pure_; +int path_get_parent(const char *path, char **parent); +bool path_is_absolute(const char *p) _pure_; +char* path_make_absolute(const char *p, const char *prefix); +char* path_make_absolute_cwd(const char *p); +char* path_kill_slashes(char *path); +char* path_startswith(const char *path, const char *prefix) _pure_; +bool path_equal(const char *a, const char *b) _pure_; + +char** path_strv_make_absolute_cwd(char **l); +char** path_strv_canonicalize(char **l); +char** path_strv_canonicalize_uniq(char **l); + +int path_is_mount_point(const char *path, bool allow_symlink); +int path_is_read_only_fs(const char *path); +int path_is_os_tree(const char *path); diff --git a/src/shared/polkit.c b/src/shared/polkit.c new file mode 100644 index 0000000000..cea7074ad3 --- /dev/null +++ b/src/shared/polkit.c @@ -0,0 +1,174 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <sys/types.h> + +#include <errno.h> + +#include "util.h" +#include "dbus-common.h" +#include "polkit.h" + +int verify_polkit( + DBusConnection *c, + DBusMessage *request, + const char *action, + bool interactive, + bool *_challenge, + DBusError *error) { + + +#ifdef ENABLE_POLKIT + DBusMessage *m = NULL, *reply = NULL; + const char *unix_process = "unix-process", *pid = "pid", *starttime = "start-time", *cancel_id = ""; + uint32_t flags = interactive ? 1 : 0; + pid_t pid_raw; + uint32_t pid_u32; + unsigned long long starttime_raw; + uint64_t starttime_u64; + DBusMessageIter iter_msg, iter_struct, iter_array, iter_dict, iter_variant; + int r; + dbus_bool_t authorized = FALSE, challenge = FALSE; +#endif + const char *sender; + unsigned long ul; + + assert(c); + assert(request); + + sender = dbus_message_get_sender(request); + if (!sender) + return -EINVAL; + + ul = dbus_bus_get_unix_user(c, sender, error); + if (ul == (unsigned long) -1) + return -EINVAL; + + /* Shortcut things for root, to avoid the PK roundtrip and dependency */ + if (ul == 0) + return 1; + +#ifdef ENABLE_POLKIT + + pid_raw = bus_get_unix_process_id(c, sender, error); + if (pid_raw == 0) + return -EINVAL; + + r = get_starttime_of_pid(pid_raw, &starttime_raw); + if (r < 0) + return r; + + m = dbus_message_new_method_call( + "org.freedesktop.PolicyKit1", + "/org/freedesktop/PolicyKit1/Authority", + "org.freedesktop.PolicyKit1.Authority", + "CheckAuthorization"); + if (!m) + return -ENOMEM; + + dbus_message_iter_init_append(m, &iter_msg); + + pid_u32 = (uint32_t) pid_raw; + starttime_u64 = (uint64_t) starttime_raw; + + if (!dbus_message_iter_open_container(&iter_msg, DBUS_TYPE_STRUCT, NULL, &iter_struct) || + !dbus_message_iter_append_basic(&iter_struct, DBUS_TYPE_STRING, &unix_process) || + !dbus_message_iter_open_container(&iter_struct, DBUS_TYPE_ARRAY, "{sv}", &iter_array) || + !dbus_message_iter_open_container(&iter_array, DBUS_TYPE_DICT_ENTRY, NULL, &iter_dict) || + !dbus_message_iter_append_basic(&iter_dict, DBUS_TYPE_STRING, &pid) || + !dbus_message_iter_open_container(&iter_dict, DBUS_TYPE_VARIANT, "u", &iter_variant) || + !dbus_message_iter_append_basic(&iter_variant, DBUS_TYPE_UINT32, &pid_u32) || + !dbus_message_iter_close_container(&iter_dict, &iter_variant) || + !dbus_message_iter_close_container(&iter_array, &iter_dict) || + !dbus_message_iter_open_container(&iter_array, DBUS_TYPE_DICT_ENTRY, NULL, &iter_dict) || + !dbus_message_iter_append_basic(&iter_dict, DBUS_TYPE_STRING, &starttime) || + !dbus_message_iter_open_container(&iter_dict, DBUS_TYPE_VARIANT, "t", &iter_variant) || + !dbus_message_iter_append_basic(&iter_variant, DBUS_TYPE_UINT64, &starttime_u64) || + !dbus_message_iter_close_container(&iter_dict, &iter_variant) || + !dbus_message_iter_close_container(&iter_array, &iter_dict) || + !dbus_message_iter_close_container(&iter_struct, &iter_array) || + !dbus_message_iter_close_container(&iter_msg, &iter_struct) || + !dbus_message_iter_append_basic(&iter_msg, DBUS_TYPE_STRING, &action) || + !dbus_message_iter_open_container(&iter_msg, DBUS_TYPE_ARRAY, "{ss}", &iter_array) || + !dbus_message_iter_close_container(&iter_msg, &iter_array) || + !dbus_message_iter_append_basic(&iter_msg, DBUS_TYPE_UINT32, &flags) || + !dbus_message_iter_append_basic(&iter_msg, DBUS_TYPE_STRING, &cancel_id)) { + r = -ENOMEM; + goto finish; + } + + reply = dbus_connection_send_with_reply_and_block(c, m, -1, error); + if (!reply) { + + /* Treat no PK available as access denied */ + if (dbus_error_has_name(error, DBUS_ERROR_SERVICE_UNKNOWN)) { + r = -EACCES; + dbus_error_free(error); + goto finish; + } + + r = -EIO; + goto finish; + } + + if (!dbus_message_iter_init(reply, &iter_msg) || + dbus_message_iter_get_arg_type(&iter_msg) != DBUS_TYPE_STRUCT) { + r = -EIO; + goto finish; + } + + dbus_message_iter_recurse(&iter_msg, &iter_struct); + + if (dbus_message_iter_get_arg_type(&iter_struct) != DBUS_TYPE_BOOLEAN) { + r = -EIO; + goto finish; + } + + dbus_message_iter_get_basic(&iter_struct, &authorized); + + if (!dbus_message_iter_next(&iter_struct) || + dbus_message_iter_get_arg_type(&iter_struct) != DBUS_TYPE_BOOLEAN) { + r = -EIO; + goto finish; + } + + dbus_message_iter_get_basic(&iter_struct, &challenge); + + if (authorized) + r = 1; + else if (_challenge) { + *_challenge = !!challenge; + r = 0; + } else + r = -EPERM; + +finish: + if (m) + dbus_message_unref(m); + + if (reply) + dbus_message_unref(reply); + + return r; +#else + return -EPERM; +#endif +} diff --git a/src/shared/polkit.h b/src/shared/polkit.h new file mode 100644 index 0000000000..6255d1f617 --- /dev/null +++ b/src/shared/polkit.h @@ -0,0 +1,33 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <stdbool.h> +#include <dbus/dbus.h> + +int verify_polkit( + DBusConnection *c, + DBusMessage *request, + const char *action, + bool interactive, + bool *challenge, + DBusError *error); diff --git a/src/shared/prioq.c b/src/shared/prioq.c new file mode 100644 index 0000000000..2d166360aa --- /dev/null +++ b/src/shared/prioq.c @@ -0,0 +1,305 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2013 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "util.h" +#include "prioq.h" + +struct prioq_item { + void *data; + unsigned *idx; +}; + +struct Prioq { + compare_func_t compare_func; + unsigned n_items, n_allocated; + + struct prioq_item *items; +}; + +Prioq *prioq_new(compare_func_t compare_func) { + Prioq *q; + + q = new0(Prioq, 1); + if (!q) + return q; + + q->compare_func = compare_func; + return q; +} + +void prioq_free(Prioq *q) { + if (!q) + return; + + free(q->items); + free(q); +} + +int prioq_ensure_allocated(Prioq **q, compare_func_t compare_func) { + assert(q); + + if (*q) + return 0; + + *q = prioq_new(compare_func); + if (!*q) + return -ENOMEM; + + return 0; +} + +static void swap(Prioq *q, unsigned j, unsigned k) { + void *saved_data; + unsigned *saved_idx; + + assert(q); + assert(j < q->n_items); + assert(k < q->n_items); + + assert(!q->items[j].idx || *(q->items[j].idx) == j); + assert(!q->items[k].idx || *(q->items[k].idx) == k); + + saved_data = q->items[j].data; + saved_idx = q->items[j].idx; + q->items[j].data = q->items[k].data; + q->items[j].idx = q->items[k].idx; + q->items[k].data = saved_data; + q->items[k].idx = saved_idx; + + if (q->items[j].idx) + *q->items[j].idx = j; + + if (q->items[k].idx) + *q->items[k].idx = k; +} + +static unsigned shuffle_up(Prioq *q, unsigned idx) { + assert(q); + + while (idx > 0) { + unsigned k; + + k = (idx-1)/2; + + if (q->compare_func(q->items[k].data, q->items[idx].data) < 0) + break; + + swap(q, idx, k); + idx = k; + } + + return idx; +} + +static unsigned shuffle_down(Prioq *q, unsigned idx) { + assert(q); + + for (;;) { + unsigned j, k, s; + + k = (idx+1)*2; /* right child */ + j = k-1; /* left child */ + + if (j >= q->n_items) + break; + + if (q->compare_func(q->items[j].data, q->items[idx].data) < 0) + + /* So our left child is smaller than we are, let's + * remember this fact */ + s = j; + else + s = idx; + + if (k < q->n_items && + q->compare_func(q->items[k].data, q->items[s].data) < 0) + + /* So our right child is smaller than we are, let's + * remember this fact */ + s = k; + + /* s now points to the smallest of the three items */ + + if (s == idx) + /* No swap necessary, we're done */ + break; + + swap(q, idx, s); + idx = s; + } + + return idx; +} + +int prioq_put(Prioq *q, void *data, unsigned *idx) { + struct prioq_item *i; + unsigned k; + + assert(q); + + if (q->n_items >= q->n_allocated) { + unsigned n; + struct prioq_item *j; + + n = MAX((q->n_items+1) * 2, 16u); + j = realloc(q->items, sizeof(struct prioq_item) * n); + if (!j) + return -ENOMEM; + + q->items = j; + q->n_allocated = n; + } + + k = q->n_items++; + i = q->items + k; + i->data = data; + i->idx = idx; + + if (idx) + *idx = k; + + shuffle_up(q, k); + + return 0; +} + +static void remove_item(Prioq *q, struct prioq_item *i) { + struct prioq_item *l; + + assert(q); + assert(i); + + l = q->items + q->n_items - 1; + + if (i == l) + /* Last entry, let's just remove it */ + q->n_items--; + else { + unsigned k; + + /* Not last entry, let's replace the last entry with + * this one, and reshuffle */ + + k = i - q->items; + + i->data = l->data; + i->idx = l->idx; + if (i->idx) + *i->idx = k; + q->n_items--; + + k = shuffle_down(q, k); + shuffle_up(q, k); + } +} + +_pure_ static struct prioq_item* find_item(Prioq *q, void *data, unsigned *idx) { + struct prioq_item *i; + + assert(q); + + if (idx) { + if (*idx > q->n_items) + return NULL; + + i = q->items + *idx; + if (i->data != data) + return NULL; + + return i; + } else { + for (i = q->items; i < q->items + q->n_items; i++) + if (i->data == data) + return i; + return NULL; + } +} + +int prioq_remove(Prioq *q, void *data, unsigned *idx) { + struct prioq_item *i; + + if (!q) + return 0; + + i = find_item(q, data, idx); + if (!i) + return 0; + + remove_item(q, i); + return 1; +} + +int prioq_reshuffle(Prioq *q, void *data, unsigned *idx) { + struct prioq_item *i; + unsigned k; + + assert(q); + + i = find_item(q, data, idx); + if (!i) + return 0; + + k = i - q->items; + k = shuffle_down(q, k); + shuffle_up(q, k); + return 1; +} + +void *prioq_peek(Prioq *q) { + + if (!q) + return NULL; + + if (q->n_items <= 0) + return NULL; + + return q->items[0].data; +} + +void *prioq_pop(Prioq *q) { + void *data; + + if (!q) + return NULL; + + if (q->n_items <= 0) + return NULL; + + data = q->items[0].data; + remove_item(q, q->items); + return data; +} + +unsigned prioq_size(Prioq *q) { + + if (!q) + return 0; + + return q->n_items; + +} +bool prioq_isempty(Prioq *q) { + + if (!q) + return true; + + return q->n_items <= 0; +} diff --git a/src/shared/prioq.h b/src/shared/prioq.h new file mode 100644 index 0000000000..4a206a3e59 --- /dev/null +++ b/src/shared/prioq.h @@ -0,0 +1,40 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "hashmap.h" + +typedef struct Prioq Prioq; + +Prioq *prioq_new(compare_func_t compare); +void prioq_free(Prioq *q); +int prioq_ensure_allocated(Prioq **q, compare_func_t compare_func); + +int prioq_put(Prioq *q, void *data, unsigned *idx); +int prioq_remove(Prioq *q, void *data, unsigned *idx); +int prioq_reshuffle(Prioq *q, void *data, unsigned *idx); + +void *prioq_peek(Prioq *q) _pure_; +void *prioq_pop(Prioq *q); + +unsigned prioq_size(Prioq *q) _pure_; +bool prioq_isempty(Prioq *q) _pure_; diff --git a/src/shared/ratelimit.c b/src/shared/ratelimit.c new file mode 100644 index 0000000000..01b62b7b38 --- /dev/null +++ b/src/shared/ratelimit.c @@ -0,0 +1,57 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <assert.h> + +#include "ratelimit.h" +#include "log.h" + +/* Modelled after Linux' lib/ratelimit.c by Dave Young + * <hidave.darkstar@gmail.com>, which is licensed GPLv2. */ + +bool ratelimit_test(RateLimit *r) { + usec_t ts; + + assert(r); + + if (r->interval <= 0 || r->burst <= 0) + return true; + + ts = now(CLOCK_MONOTONIC); + + if (r->begin <= 0 || + r->begin + r->interval < ts) { + r->begin = ts; + + /* Reset counter */ + r->num = 0; + goto good; + } + + if (r->num < r->burst) + goto good; + + return false; + +good: + r->num++; + return true; +} diff --git a/src/shared/ratelimit.h b/src/shared/ratelimit.h new file mode 100644 index 0000000000..58efca7df1 --- /dev/null +++ b/src/shared/ratelimit.h @@ -0,0 +1,57 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "util.h" + +typedef struct RateLimit { + usec_t interval; + usec_t begin; + unsigned burst; + unsigned num; +} RateLimit; + +#define RATELIMIT_DEFINE(_name, _interval, _burst) \ + RateLimit _name = { \ + .interval = (_interval), \ + .burst = (_burst), \ + .num = 0, \ + .begin = 0 \ + } + +#define RATELIMIT_INIT(v, _interval, _burst) \ + do { \ + RateLimit *_r = &(v); \ + _r->interval = (_interval); \ + _r->burst = (_burst); \ + _r->num = 0; \ + _r->begin = 0; \ + } while (false) + +#define RATELIMIT_RESET(v) \ + do { \ + RateLimit *_r = &(v); \ + _r->num = 0; \ + _r->begin = 0; \ + } while (false) + +bool ratelimit_test(RateLimit *r); diff --git a/src/shared/replace-var.c b/src/shared/replace-var.c new file mode 100644 index 0000000000..e11c57a43d --- /dev/null +++ b/src/shared/replace-var.c @@ -0,0 +1,110 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2012 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <string.h> + +#include "macro.h" +#include "util.h" +#include "replace-var.h" + +/* + * Generic infrastructure for replacing @FOO@ style variables in + * strings. Will call a callback for each replacement. + */ + +static int get_variable(const char *b, char **r) { + size_t k; + char *t; + + assert(b); + assert(r); + + if (*b != '@') + return 0; + + k = strspn(b + 1, "ABCDEFGHIJKLMNOPQRSTUVWXYZ_"); + if (k <= 0 || b[k+1] != '@') + return 0; + + t = strndup(b + 1, k); + if (!t) + return -ENOMEM; + + *r = t; + return 1; +} + +char *replace_var(const char *text, char *(*lookup)(const char *variable, void*userdata), void *userdata) { + char *r, *t; + const char *f; + size_t l; + + assert(text); + assert(lookup); + + l = strlen(text); + r = new(char, l+1); + if (!r) + return NULL; + + f = text; + t = r; + while (*f) { + _cleanup_free_ char *v = NULL, *n = NULL; + char *a; + int k; + size_t skip, d, nl; + + k = get_variable(f, &v); + if (k < 0) + goto oom; + if (k == 0) { + *(t++) = *(f++); + continue; + } + + n = lookup(v, userdata); + if (!n) + goto oom; + + skip = strlen(v) + 2; + + d = t - r; + nl = l - skip + strlen(n); + a = realloc(r, nl + 1); + if (!a) + goto oom; + + l = nl; + r = a; + t = r + d; + + t = stpcpy(t, n); + f += skip; + } + + *t = 0; + return r; + +oom: + free(r); + return NULL; +} diff --git a/src/shared/replace-var.h b/src/shared/replace-var.h new file mode 100644 index 0000000000..7eaee93a3e --- /dev/null +++ b/src/shared/replace-var.h @@ -0,0 +1,24 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2012 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +char *replace_var(const char *text, char *(*lookup)(const char *variable, void*userdata), void *userdata); diff --git a/src/shared/selinux-util.c b/src/shared/selinux-util.c new file mode 100644 index 0000000000..ff3375607f --- /dev/null +++ b/src/shared/selinux-util.c @@ -0,0 +1,42 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "selinux-util.h" + +#ifdef HAVE_SELINUX + +#include <selinux/selinux.h> + +static int use_selinux_cached = -1; + +bool use_selinux(void) { + + if (use_selinux_cached < 0) + use_selinux_cached = is_selinux_enabled() > 0; + + return use_selinux_cached; +} + +void retest_selinux(void) { + use_selinux_cached = -1; +} + +#endif diff --git a/src/shared/selinux-util.h b/src/shared/selinux-util.h new file mode 100644 index 0000000000..4b81202741 --- /dev/null +++ b/src/shared/selinux-util.h @@ -0,0 +1,27 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <stdbool.h> + +bool use_selinux(void); +void retest_selinux(void); diff --git a/src/shared/set.c b/src/shared/set.c new file mode 100644 index 0000000000..c338dc3a44 --- /dev/null +++ b/src/shared/set.c @@ -0,0 +1,137 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <stdlib.h> + +#include "set.h" +#include "hashmap.h" + +#define MAKE_SET(h) ((Set*) (h)) +#define MAKE_HASHMAP(s) ((Hashmap*) (s)) + +/* For now this is not much more than a wrapper around a hashmap */ + +Set *set_new(hash_func_t hash_func, compare_func_t compare_func) { + return MAKE_SET(hashmap_new(hash_func, compare_func)); +} + +void set_free(Set* s) { + hashmap_free(MAKE_HASHMAP(s)); +} + +void set_free_free(Set *s) { + hashmap_free_free(MAKE_HASHMAP(s)); +} + +int set_ensure_allocated(Set **s, hash_func_t hash_func, compare_func_t compare_func) { + return hashmap_ensure_allocated((Hashmap**) s, hash_func, compare_func); +} + +int set_put(Set *s, void *value) { + return hashmap_put(MAKE_HASHMAP(s), value, value); +} + +int set_consume(Set *s, void *value) { + int r = set_put(s, value); + if (r < 0) + free(value); + return r; +} + +int set_replace(Set *s, void *value) { + return hashmap_replace(MAKE_HASHMAP(s), value, value); +} + +void *set_get(Set *s, void *value) { + return hashmap_get(MAKE_HASHMAP(s), value); +} + +bool set_contains(Set *s, void *value) { + return hashmap_contains(MAKE_HASHMAP(s), value); +} + +void *set_remove(Set *s, void *value) { + return hashmap_remove(MAKE_HASHMAP(s), value); +} + +int set_remove_and_put(Set *s, void *old_value, void *new_value) { + return hashmap_remove_and_put(MAKE_HASHMAP(s), old_value, new_value, new_value); +} + +unsigned set_size(Set *s) { + return hashmap_size(MAKE_HASHMAP(s)); +} + +bool set_isempty(Set *s) { + return hashmap_isempty(MAKE_HASHMAP(s)); +} + +void *set_iterate(Set *s, Iterator *i) { + return hashmap_iterate(MAKE_HASHMAP(s), i, NULL); +} + +void *set_iterate_backwards(Set *s, Iterator *i) { + return hashmap_iterate_backwards(MAKE_HASHMAP(s), i, NULL); +} + +void *set_iterate_skip(Set *s, void *value, Iterator *i) { + return hashmap_iterate_skip(MAKE_HASHMAP(s), value, i); +} + +void *set_steal_first(Set *s) { + return hashmap_steal_first(MAKE_HASHMAP(s)); +} + +void* set_first(Set *s) { + return hashmap_first(MAKE_HASHMAP(s)); +} + +void* set_last(Set *s) { + return hashmap_last(MAKE_HASHMAP(s)); +} + +int set_merge(Set *s, Set *other) { + return hashmap_merge(MAKE_HASHMAP(s), MAKE_HASHMAP(other)); +} + +void set_move(Set *s, Set *other) { + return hashmap_move(MAKE_HASHMAP(s), MAKE_HASHMAP(other)); +} + +int set_move_one(Set *s, Set *other, void *value) { + return hashmap_move_one(MAKE_HASHMAP(s), MAKE_HASHMAP(other), value); +} + +Set* set_copy(Set *s) { + return MAKE_SET(hashmap_copy(MAKE_HASHMAP(s))); +} + +void set_clear(Set *s) { + hashmap_clear(MAKE_HASHMAP(s)); +} + +void set_clear_free(Set *s) { + hashmap_clear_free(MAKE_HASHMAP(s)); +} + +char **set_get_strv(Set *s) { + return hashmap_get_strv(MAKE_HASHMAP(s)); +} diff --git a/src/shared/set.h b/src/shared/set.h new file mode 100644 index 0000000000..e5d46e9a8f --- /dev/null +++ b/src/shared/set.h @@ -0,0 +1,83 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +/* Pretty straightforward set implementation. Internally based on the + * hashmap. That means that as a minor optimization a NULL set + * object will be treated as empty set for all read + * operations. That way it is not necessary to instantiate an object + * for each set use. */ + +#include "hashmap.h" + +typedef struct Set Set; + +Set *set_new(hash_func_t hash_func, compare_func_t compare_func); +void set_free(Set* s); +static inline void set_freep(Set **s) { + set_free(*s); +} + +void set_free_free(Set *s); +static inline void set_free_freep(Set **s) { + set_free_free(*s); +} + +Set* set_copy(Set *s); +int set_ensure_allocated(Set **s, hash_func_t hash_func, compare_func_t compare_func); + +int set_put(Set *s, void *value); +int set_consume(Set *s, void *value); +int set_replace(Set *s, void *value); +void *set_get(Set *s, void *value); +bool set_contains(Set *s, void *value); +void *set_remove(Set *s, void *value); +int set_remove_and_put(Set *s, void *old_value, void *new_value); + +int set_merge(Set *s, Set *other); +void set_move(Set *s, Set *other); +int set_move_one(Set *s, Set *other, void *value); + +unsigned set_size(Set *s); +bool set_isempty(Set *s); + +void *set_iterate(Set *s, Iterator *i); +void *set_iterate_backwards(Set *s, Iterator *i); +void *set_iterate_skip(Set *s, void *value, Iterator *i); + +void set_clear(Set *s); +void set_clear_free(Set *s); + +void *set_steal_first(Set *s); +void* set_first(Set *s); +void* set_last(Set *s); + +char **set_get_strv(Set *s); + +#define SET_FOREACH(e, s, i) \ + for ((i) = ITERATOR_FIRST, (e) = set_iterate((s), &(i)); (e); (e) = set_iterate((s), &(i))) + +#define SET_FOREACH_BACKWARDS(e, s, i) \ + for ((i) = ITERATOR_LAST, (e) = set_iterate_backwards((s), &(i)); (e); (e) = set_iterate_backwards((s), &(i))) + +#define _cleanup_set_free_ _cleanup_(set_freep) +#define _cleanup_set_free_free_ _cleanup_(set_free_freep) diff --git a/src/shared/sleep-config.c b/src/shared/sleep-config.c new file mode 100644 index 0000000000..cd3238b405 --- /dev/null +++ b/src/shared/sleep-config.c @@ -0,0 +1,179 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2013 Zbigniew JÄ™drzejewski-Szmek + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <stdio.h> + +#include "conf-parser.h" +#include "sleep-config.h" +#include "fileio.h" +#include "log.h" +#include "strv.h" +#include "util.h" + +int parse_sleep_config(const char *verb, char ***modes, char ***states) { + _cleanup_strv_free_ char + **suspend_mode = NULL, **suspend_state = NULL, + **hibernate_mode = NULL, **hibernate_state = NULL, + **hybrid_mode = NULL, **hybrid_state = NULL; + + const ConfigTableItem items[] = { + { "Sleep", "SuspendMode", config_parse_strv, 0, &suspend_mode }, + { "Sleep", "SuspendState", config_parse_strv, 0, &suspend_state }, + { "Sleep", "HibernateMode", config_parse_strv, 0, &hibernate_mode }, + { "Sleep", "HibernateState", config_parse_strv, 0, &hibernate_state }, + { "Sleep", "HybridSleepMode", config_parse_strv, 0, &hybrid_mode }, + { "Sleep", "HybridSleepState", config_parse_strv, 0, &hybrid_state }, + {}}; + + int r; + FILE _cleanup_fclose_ *f; + + f = fopen(PKGSYSCONFDIR "/sleep.conf", "re"); + if (!f) + log_full(errno == ENOENT ? LOG_DEBUG: LOG_WARNING, + "Failed to open configuration file " PKGSYSCONFDIR "/sleep.conf: %m"); + else { + r = config_parse(NULL, PKGSYSCONFDIR "/sleep.conf", f, "Sleep\0", + config_item_table_lookup, (void*) items, false, false, NULL); + if (r < 0) + log_warning("Failed to parse configuration file: %s", strerror(-r)); + } + + if (streq(verb, "suspend")) { + /* empty by default */ + *modes = suspend_mode; + + if (suspend_state) + *states = suspend_state; + else + *states = strv_split_nulstr("mem\0standby\0freeze\0"); + + suspend_mode = suspend_state = NULL; + } else if (streq(verb, "hibernate")) { + if (hibernate_mode) + *modes = hibernate_mode; + else + *modes = strv_split_nulstr("platform\0shutdown\0"); + + if (hibernate_state) + *states = hibernate_state; + else + *states = strv_split_nulstr("disk\0"); + + hibernate_mode = hibernate_state = NULL; + } else if (streq(verb, "hybrid-sleep")) { + if (hybrid_mode) + *modes = hybrid_mode; + else + *modes = strv_split_nulstr("suspend\0platform\0shutdown\0"); + + if (hybrid_state) + *states = hybrid_state; + else + *states = strv_split_nulstr("disk\0"); + + hybrid_mode = hybrid_state = NULL; + } else + assert_not_reached("what verb"); + + if (!modes || !states) { + strv_free(*modes); + strv_free(*states); + return log_oom(); + } + + return 0; +} + +int can_sleep_state(char **types) { + char *w, *state, **type; + int r; + _cleanup_free_ char *p = NULL; + + if (strv_isempty(types)) + return true; + + /* If /sys is read-only we cannot sleep */ + if (access("/sys/power/state", W_OK) < 0) + return false; + + r = read_one_line_file("/sys/power/state", &p); + if (r < 0) + return false; + + STRV_FOREACH(type, types) { + size_t l, k; + + k = strlen(*type); + FOREACH_WORD_SEPARATOR(w, l, p, WHITESPACE, state) + if (l == k && memcmp(w, *type, l) == 0) + return true; + } + + return false; +} + +int can_sleep_disk(char **types) { + char *w, *state, **type; + int r; + _cleanup_free_ char *p = NULL; + + if (strv_isempty(types)) + return true; + + /* If /sys is read-only we cannot sleep */ + if (access("/sys/power/disk", W_OK) < 0) + return false; + + r = read_one_line_file("/sys/power/disk", &p); + if (r < 0) + return false; + + STRV_FOREACH(type, types) { + size_t l, k; + + k = strlen(*type); + FOREACH_WORD_SEPARATOR(w, l, p, WHITESPACE, state) { + if (l == k && memcmp(w, *type, l) == 0) + return true; + + if (l == k + 2 && w[0] == '[' && memcmp(w + 1, *type, l - 2) == 0 && w[l-1] == ']') + return true; + } + } + + return false; +} + +int can_sleep(const char *verb) { + _cleanup_strv_free_ char **modes = NULL, **states = NULL; + int r; + + assert(streq(verb, "suspend") || + streq(verb, "hibernate") || + streq(verb, "hybrid-sleep")); + + r = parse_sleep_config(verb, &modes, &states); + if (r < 0) + return false; + + return can_sleep_state(states) && can_sleep_disk(modes); +} diff --git a/src/shared/sleep-config.h b/src/shared/sleep-config.h new file mode 100644 index 0000000000..51d2dec7b4 --- /dev/null +++ b/src/shared/sleep-config.h @@ -0,0 +1,26 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2013 Zbigniew JÄ™drzejewski-Szmek + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +int parse_sleep_config(const char *verb, char ***modes, char ***states); + +int can_sleep(const char *verb); +int can_sleep_disk(char **types); +int can_sleep_state(char **types); diff --git a/src/shared/socket-label.c b/src/shared/socket-label.c new file mode 100644 index 0000000000..ff212de825 --- /dev/null +++ b/src/shared/socket-label.c @@ -0,0 +1,143 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <assert.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <stdlib.h> +#include <arpa/inet.h> +#include <stdio.h> +#include <net/if.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <stddef.h> +#include <sys/ioctl.h> + +#include "macro.h" +#include "util.h" +#include "mkdir.h" +#include "socket-util.h" +#include "missing.h" +#include "label.h" + +int socket_address_listen( + const SocketAddress *a, + int backlog, + SocketAddressBindIPv6Only only, + const char *bind_to_device, + bool free_bind, + bool transparent, + mode_t directory_mode, + mode_t socket_mode, + const char *label, + int *ret) { + + int r, fd, one; + assert(a); + assert(ret); + + if ((r = socket_address_verify(a)) < 0) + return r; + + if (socket_address_family(a) == AF_INET6 && !socket_ipv6_is_supported()) + return -EAFNOSUPPORT; + + r = label_socket_set(label); + if (r < 0) + return r; + + fd = socket(socket_address_family(a), a->type | SOCK_NONBLOCK | SOCK_CLOEXEC, a->protocol); + r = fd < 0 ? -errno : 0; + + label_socket_clear(); + + if (r < 0) + return r; + + if (socket_address_family(a) == AF_INET6 && only != SOCKET_ADDRESS_DEFAULT) { + int flag = only == SOCKET_ADDRESS_IPV6_ONLY; + + if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &flag, sizeof(flag)) < 0) + goto fail; + } + + if (socket_address_family(a) == AF_INET || socket_address_family(a) == AF_INET6) { + if (bind_to_device) + if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, bind_to_device, strlen(bind_to_device)+1) < 0) + goto fail; + + if (free_bind) { + one = 1; + if (setsockopt(fd, IPPROTO_IP, IP_FREEBIND, &one, sizeof(one)) < 0) + log_warning("IP_FREEBIND failed: %m"); + } + + if (transparent) { + one = 1; + if (setsockopt(fd, IPPROTO_IP, IP_TRANSPARENT, &one, sizeof(one)) < 0) + log_warning("IP_TRANSPARENT failed: %m"); + } + } + + one = 1; + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) < 0) + goto fail; + + if (socket_address_family(a) == AF_UNIX && a->sockaddr.un.sun_path[0] != 0) { + mode_t old_mask; + + /* Create parents */ + mkdir_parents_label(a->sockaddr.un.sun_path, directory_mode); + + /* Enforce the right access mode for the socket*/ + old_mask = umask(~ socket_mode); + + /* Include the original umask in our mask */ + umask(~socket_mode | old_mask); + + r = label_bind(fd, &a->sockaddr.sa, a->size); + + if (r < 0 && errno == EADDRINUSE) { + /* Unlink and try again */ + unlink(a->sockaddr.un.sun_path); + r = bind(fd, &a->sockaddr.sa, a->size); + } + + umask(old_mask); + } else + r = bind(fd, &a->sockaddr.sa, a->size); + + if (r < 0) + goto fail; + + if (socket_address_can_accept(a)) + if (listen(fd, backlog) < 0) + goto fail; + + *ret = fd; + return 0; + +fail: + r = -errno; + close_nointr_nofail(fd); + return r; +} diff --git a/src/shared/socket-util.c b/src/shared/socket-util.c new file mode 100644 index 0000000000..c583d3dfea --- /dev/null +++ b/src/shared/socket-util.c @@ -0,0 +1,635 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <assert.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <stdlib.h> +#include <arpa/inet.h> +#include <stdio.h> +#include <net/if.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <stddef.h> +#include <sys/ioctl.h> + +#include "macro.h" +#include "util.h" +#include "mkdir.h" +#include "path-util.h" +#include "socket-util.h" +#include "missing.h" +#include "fileio.h" + +int socket_address_parse(SocketAddress *a, const char *s) { + int r; + char *e, *n; + unsigned u; + + assert(a); + assert(s); + + zero(*a); + a->type = SOCK_STREAM; + + if (*s == '[') { + /* IPv6 in [x:.....:z]:p notation */ + + if (!socket_ipv6_is_supported()) { + log_warning("Binding to IPv6 address not available since kernel does not support IPv6."); + return -EAFNOSUPPORT; + } + + if (!(e = strchr(s+1, ']'))) + return -EINVAL; + + if (!(n = strndup(s+1, e-s-1))) + return -ENOMEM; + + errno = 0; + if (inet_pton(AF_INET6, n, &a->sockaddr.in6.sin6_addr) <= 0) { + free(n); + return errno > 0 ? -errno : -EINVAL; + } + + free(n); + + e++; + if (*e != ':') + return -EINVAL; + + e++; + if ((r = safe_atou(e, &u)) < 0) + return r; + + if (u <= 0 || u > 0xFFFF) + return -EINVAL; + + a->sockaddr.in6.sin6_family = AF_INET6; + a->sockaddr.in6.sin6_port = htons((uint16_t) u); + a->size = sizeof(struct sockaddr_in6); + + } else if (*s == '/') { + /* AF_UNIX socket */ + + size_t l; + + l = strlen(s); + if (l >= sizeof(a->sockaddr.un.sun_path)) + return -EINVAL; + + a->sockaddr.un.sun_family = AF_UNIX; + memcpy(a->sockaddr.un.sun_path, s, l); + a->size = offsetof(struct sockaddr_un, sun_path) + l + 1; + + } else if (*s == '@') { + /* Abstract AF_UNIX socket */ + size_t l; + + l = strlen(s+1); + if (l >= sizeof(a->sockaddr.un.sun_path) - 1) + return -EINVAL; + + a->sockaddr.un.sun_family = AF_UNIX; + memcpy(a->sockaddr.un.sun_path+1, s+1, l); + a->size = offsetof(struct sockaddr_un, sun_path) + 1 + l; + + } else { + + if ((e = strchr(s, ':'))) { + + if ((r = safe_atou(e+1, &u)) < 0) + return r; + + if (u <= 0 || u > 0xFFFF) + return -EINVAL; + + if (!(n = strndup(s, e-s))) + return -ENOMEM; + + /* IPv4 in w.x.y.z:p notation? */ + if ((r = inet_pton(AF_INET, n, &a->sockaddr.in4.sin_addr)) < 0) { + free(n); + return -errno; + } + + if (r > 0) { + /* Gotcha, it's a traditional IPv4 address */ + free(n); + + a->sockaddr.in4.sin_family = AF_INET; + a->sockaddr.in4.sin_port = htons((uint16_t) u); + a->size = sizeof(struct sockaddr_in); + } else { + unsigned idx; + + if (strlen(n) > IF_NAMESIZE-1) { + free(n); + return -EINVAL; + } + + /* Uh, our last resort, an interface name */ + idx = if_nametoindex(n); + free(n); + + if (idx == 0) + return -EINVAL; + + if (!socket_ipv6_is_supported()) { + log_warning("Binding to interface is not available since kernel does not support IPv6."); + return -EAFNOSUPPORT; + } + + a->sockaddr.in6.sin6_family = AF_INET6; + a->sockaddr.in6.sin6_port = htons((uint16_t) u); + a->sockaddr.in6.sin6_scope_id = idx; + a->sockaddr.in6.sin6_addr = in6addr_any; + a->size = sizeof(struct sockaddr_in6); + } + } else { + + /* Just a port */ + r = safe_atou(s, &u); + if (r < 0) + return r; + + if (u <= 0 || u > 0xFFFF) + return -EINVAL; + + if (socket_ipv6_is_supported()) { + a->sockaddr.in6.sin6_family = AF_INET6; + a->sockaddr.in6.sin6_port = htons((uint16_t) u); + a->sockaddr.in6.sin6_addr = in6addr_any; + a->size = sizeof(struct sockaddr_in6); + } else { + a->sockaddr.in4.sin_family = AF_INET; + a->sockaddr.in4.sin_port = htons((uint16_t) u); + a->sockaddr.in4.sin_addr.s_addr = INADDR_ANY; + a->size = sizeof(struct sockaddr_in); + } + } + } + + return 0; +} + +int socket_address_parse_netlink(SocketAddress *a, const char *s) { + int family; + unsigned group = 0; + _cleanup_free_ char *sfamily = NULL; + assert(a); + assert(s); + + zero(*a); + a->type = SOCK_RAW; + + errno = 0; + if (sscanf(s, "%ms %u", &sfamily, &group) < 1) + return errno > 0 ? -errno : -EINVAL; + + family = netlink_family_from_string(sfamily); + if (family < 0) + return -EINVAL; + + a->sockaddr.nl.nl_family = AF_NETLINK; + a->sockaddr.nl.nl_groups = group; + + a->type = SOCK_RAW; + a->size = sizeof(struct sockaddr_nl); + a->protocol = family; + + return 0; +} + +int socket_address_verify(const SocketAddress *a) { + assert(a); + + switch (socket_address_family(a)) { + + case AF_INET: + if (a->size != sizeof(struct sockaddr_in)) + return -EINVAL; + + if (a->sockaddr.in4.sin_port == 0) + return -EINVAL; + + if (a->type != SOCK_STREAM && a->type != SOCK_DGRAM) + return -EINVAL; + + return 0; + + case AF_INET6: + if (a->size != sizeof(struct sockaddr_in6)) + return -EINVAL; + + if (a->sockaddr.in6.sin6_port == 0) + return -EINVAL; + + if (a->type != SOCK_STREAM && a->type != SOCK_DGRAM) + return -EINVAL; + + return 0; + + case AF_UNIX: + if (a->size < offsetof(struct sockaddr_un, sun_path)) + return -EINVAL; + + if (a->size > offsetof(struct sockaddr_un, sun_path)) { + + if (a->sockaddr.un.sun_path[0] != 0) { + char *e; + + /* path */ + if (!(e = memchr(a->sockaddr.un.sun_path, 0, sizeof(a->sockaddr.un.sun_path)))) + return -EINVAL; + + if (a->size != offsetof(struct sockaddr_un, sun_path) + (e - a->sockaddr.un.sun_path) + 1) + return -EINVAL; + } + } + + if (a->type != SOCK_STREAM && a->type != SOCK_DGRAM && a->type != SOCK_SEQPACKET) + return -EINVAL; + + return 0; + + case AF_NETLINK: + + if (a->size != sizeof(struct sockaddr_nl)) + return -EINVAL; + + if (a->type != SOCK_RAW && a->type != SOCK_DGRAM) + return -EINVAL; + + return 0; + + default: + return -EAFNOSUPPORT; + } +} + +int socket_address_print(const SocketAddress *a, char **p) { + int r; + assert(a); + assert(p); + + if ((r = socket_address_verify(a)) < 0) + return r; + + switch (socket_address_family(a)) { + + case AF_INET: { + char *ret; + + if (!(ret = new(char, INET_ADDRSTRLEN+1+5+1))) + return -ENOMEM; + + if (!inet_ntop(AF_INET, &a->sockaddr.in4.sin_addr, ret, INET_ADDRSTRLEN)) { + free(ret); + return -errno; + } + + sprintf(strchr(ret, 0), ":%u", ntohs(a->sockaddr.in4.sin_port)); + *p = ret; + return 0; + } + + case AF_INET6: { + char *ret; + + if (!(ret = new(char, 1+INET6_ADDRSTRLEN+2+5+1))) + return -ENOMEM; + + ret[0] = '['; + if (!inet_ntop(AF_INET6, &a->sockaddr.in6.sin6_addr, ret+1, INET6_ADDRSTRLEN)) { + free(ret); + return -errno; + } + + sprintf(strchr(ret, 0), "]:%u", ntohs(a->sockaddr.in6.sin6_port)); + *p = ret; + return 0; + } + + case AF_UNIX: { + char *ret; + + if (a->size <= offsetof(struct sockaddr_un, sun_path)) { + + if (!(ret = strdup("<unnamed>"))) + return -ENOMEM; + + } else if (a->sockaddr.un.sun_path[0] == 0) { + /* abstract */ + + /* FIXME: We assume we can print the + * socket path here and that it hasn't + * more than one NUL byte. That is + * actually an invalid assumption */ + + if (!(ret = new(char, sizeof(a->sockaddr.un.sun_path)+1))) + return -ENOMEM; + + ret[0] = '@'; + memcpy(ret+1, a->sockaddr.un.sun_path+1, sizeof(a->sockaddr.un.sun_path)-1); + ret[sizeof(a->sockaddr.un.sun_path)] = 0; + + } else { + + if (!(ret = strdup(a->sockaddr.un.sun_path))) + return -ENOMEM; + } + + *p = ret; + return 0; + } + + case AF_NETLINK: { + _cleanup_free_ char *sfamily = NULL; + + r = netlink_family_to_string_alloc(a->protocol, &sfamily); + if (r < 0) + return r; + r = asprintf(p, "%s %u", sfamily, a->sockaddr.nl.nl_groups); + if (r < 0) + return -ENOMEM; + + return 0; + } + + default: + return -EINVAL; + } +} + +bool socket_address_can_accept(const SocketAddress *a) { + assert(a); + + return + a->type == SOCK_STREAM || + a->type == SOCK_SEQPACKET; +} + +bool socket_address_equal(const SocketAddress *a, const SocketAddress *b) { + assert(a); + assert(b); + + /* Invalid addresses are unequal to all */ + if (socket_address_verify(a) < 0 || + socket_address_verify(b) < 0) + return false; + + if (a->type != b->type) + return false; + + if (a->size != b->size) + return false; + + if (socket_address_family(a) != socket_address_family(b)) + return false; + + switch (socket_address_family(a)) { + + case AF_INET: + if (a->sockaddr.in4.sin_addr.s_addr != b->sockaddr.in4.sin_addr.s_addr) + return false; + + if (a->sockaddr.in4.sin_port != b->sockaddr.in4.sin_port) + return false; + + break; + + case AF_INET6: + if (memcmp(&a->sockaddr.in6.sin6_addr, &b->sockaddr.in6.sin6_addr, sizeof(a->sockaddr.in6.sin6_addr)) != 0) + return false; + + if (a->sockaddr.in6.sin6_port != b->sockaddr.in6.sin6_port) + return false; + + break; + + case AF_UNIX: + + if ((a->sockaddr.un.sun_path[0] == 0) != (b->sockaddr.un.sun_path[0] == 0)) + return false; + + if (a->sockaddr.un.sun_path[0]) { + if (!strneq(a->sockaddr.un.sun_path, b->sockaddr.un.sun_path, sizeof(a->sockaddr.un.sun_path))) + return false; + } else { + if (memcmp(a->sockaddr.un.sun_path, b->sockaddr.un.sun_path, a->size) != 0) + return false; + } + + break; + + case AF_NETLINK: + + if (a->protocol != b->protocol) + return false; + + if (a->sockaddr.nl.nl_groups != b->sockaddr.nl.nl_groups) + return false; + + break; + + default: + /* Cannot compare, so we assume the addresses are different */ + return false; + } + + return true; +} + +bool socket_address_is(const SocketAddress *a, const char *s, int type) { + struct SocketAddress b; + + assert(a); + assert(s); + + if (socket_address_parse(&b, s) < 0) + return false; + + b.type = type; + + return socket_address_equal(a, &b); +} + +bool socket_address_is_netlink(const SocketAddress *a, const char *s) { + struct SocketAddress b; + + assert(a); + assert(s); + + if (socket_address_parse_netlink(&b, s) < 0) + return false; + + return socket_address_equal(a, &b); +} + +bool socket_address_needs_mount(const SocketAddress *a, const char *prefix) { + assert(a); + + if (socket_address_family(a) != AF_UNIX) + return false; + + if (a->sockaddr.un.sun_path[0] == 0) + return false; + + return path_startswith(a->sockaddr.un.sun_path, prefix); +} + +bool socket_ipv6_is_supported(void) { + char *l = 0; + bool enabled; + + if (access("/sys/module/ipv6", F_OK) != 0) + return 0; + + /* If we can't check "disable" parameter, assume enabled */ + if (read_one_line_file("/sys/module/ipv6/parameters/disable", &l) < 0) + return 1; + + /* If module was loaded with disable=1 no IPv6 available */ + enabled = l[0] == '0'; + free(l); + + return enabled; +} + +bool socket_address_matches_fd(const SocketAddress *a, int fd) { + union sockaddr_union sa; + socklen_t salen = sizeof(sa), solen; + int protocol, type; + + assert(a); + assert(fd >= 0); + + if (getsockname(fd, &sa.sa, &salen) < 0) + return false; + + if (sa.sa.sa_family != a->sockaddr.sa.sa_family) + return false; + + solen = sizeof(type); + if (getsockopt(fd, SOL_SOCKET, SO_TYPE, &type, &solen) < 0) + return false; + + if (type != a->type) + return false; + + if (a->protocol != 0) { + solen = sizeof(protocol); + if (getsockopt(fd, SOL_SOCKET, SO_PROTOCOL, &protocol, &solen) < 0) + return false; + + if (protocol != a->protocol) + return false; + } + + switch (sa.sa.sa_family) { + + case AF_INET: + return sa.in4.sin_port == a->sockaddr.in4.sin_port && + sa.in4.sin_addr.s_addr == a->sockaddr.in4.sin_addr.s_addr; + + case AF_INET6: + return sa.in6.sin6_port == a->sockaddr.in6.sin6_port && + memcmp(&sa.in6.sin6_addr, &a->sockaddr.in6.sin6_addr, sizeof(struct in6_addr)) == 0; + + case AF_UNIX: + return salen == a->size && + memcmp(sa.un.sun_path, a->sockaddr.un.sun_path, salen - offsetof(struct sockaddr_un, sun_path)) == 0; + + } + + return false; +} + +int make_socket_fd(const char* address, int flags) { + SocketAddress a; + int fd, r; + _cleanup_free_ char *p = NULL; + + r = socket_address_parse(&a, address); + if (r < 0) { + log_error("failed to parse socket: %s", strerror(-r)); + return r; + } + + fd = socket(socket_address_family(&a), flags, 0); + if (fd < 0) { + log_error("socket(): %m"); + return -errno; + } + + r = socket_address_print(&a, &p); + if (r < 0) { + log_error("socket_address_print(): %s", strerror(-r)); + return r; + } + log_info("Listening on %s", p); + + r = bind(fd, &a.sockaddr.sa, a.size); + if (r < 0) { + log_error("bind to %s: %m", address); + return -errno; + } + + r = listen(fd, SOMAXCONN); + if (r < 0) { + log_error("listen on %s: %m", address); + return -errno; + } + + return fd; +} + +static const char* const netlink_family_table[] = { + [NETLINK_ROUTE] = "route", + [NETLINK_FIREWALL] = "firewall", + [NETLINK_INET_DIAG] = "inet-diag", + [NETLINK_NFLOG] = "nflog", + [NETLINK_XFRM] = "xfrm", + [NETLINK_SELINUX] = "selinux", + [NETLINK_ISCSI] = "iscsi", + [NETLINK_AUDIT] = "audit", + [NETLINK_FIB_LOOKUP] = "fib-lookup", + [NETLINK_CONNECTOR] = "connector", + [NETLINK_NETFILTER] = "netfilter", + [NETLINK_IP6_FW] = "ip6-fw", + [NETLINK_DNRTMSG] = "dnrtmsg", + [NETLINK_KOBJECT_UEVENT] = "kobject-uevent", + [NETLINK_GENERIC] = "generic", + [NETLINK_SCSITRANSPORT] = "scsitransport", + [NETLINK_ECRYPTFS] = "ecryptfs" +}; + +DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(netlink_family, int, INT_MAX); + +static const char* const socket_address_bind_ipv6_only_table[_SOCKET_ADDRESS_BIND_IPV6_ONLY_MAX] = { + [SOCKET_ADDRESS_DEFAULT] = "default", + [SOCKET_ADDRESS_BOTH] = "both", + [SOCKET_ADDRESS_IPV6_ONLY] = "ipv6-only" +}; + +DEFINE_STRING_TABLE_LOOKUP(socket_address_bind_ipv6_only, SocketAddressBindIPv6Only); diff --git a/src/shared/socket-util.h b/src/shared/socket-util.h new file mode 100644 index 0000000000..7829a337fc --- /dev/null +++ b/src/shared/socket-util.h @@ -0,0 +1,103 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <sys/socket.h> +#include <netinet/in.h> +#include <sys/un.h> +#include <net/if.h> +#include <asm/types.h> +#include <linux/netlink.h> + +#include "macro.h" +#include "util.h" + +union sockaddr_union { + struct sockaddr sa; + struct sockaddr_in in4; + struct sockaddr_in6 in6; + struct sockaddr_un un; + struct sockaddr_nl nl; + struct sockaddr_storage storage; +}; + +typedef struct SocketAddress { + union sockaddr_union sockaddr; + + /* We store the size here explicitly due to the weird + * sockaddr_un semantics for abstract sockets */ + socklen_t size; + + /* Socket type, i.e. SOCK_STREAM, SOCK_DGRAM, ... */ + int type; + + /* Socket protocol, IPPROTO_xxx, usually 0, except for netlink */ + int protocol; +} SocketAddress; + +typedef enum SocketAddressBindIPv6Only { + SOCKET_ADDRESS_DEFAULT, + SOCKET_ADDRESS_BOTH, + SOCKET_ADDRESS_IPV6_ONLY, + _SOCKET_ADDRESS_BIND_IPV6_ONLY_MAX, + _SOCKET_ADDRESS_BIND_IPV6_ONLY_INVALID = -1 +} SocketAddressBindIPv6Only; + +#define socket_address_family(a) ((a)->sockaddr.sa.sa_family) + +int socket_address_parse(SocketAddress *a, const char *s); +int socket_address_parse_netlink(SocketAddress *a, const char *s); +int socket_address_print(const SocketAddress *a, char **p); +int socket_address_verify(const SocketAddress *a) _pure_; + +bool socket_address_can_accept(const SocketAddress *a) _pure_; + +int socket_address_listen( + const SocketAddress *a, + int backlog, + SocketAddressBindIPv6Only only, + const char *bind_to_device, + bool free_bind, + bool transparent, + mode_t directory_mode, + mode_t socket_mode, + const char *label, + int *ret); + +bool socket_address_is(const SocketAddress *a, const char *s, int type); +bool socket_address_is_netlink(const SocketAddress *a, const char *s); + +bool socket_address_matches_fd(const SocketAddress *a, int fd); + +int make_socket_fd(const char* address, int flags); + +bool socket_address_equal(const SocketAddress *a, const SocketAddress *b) _pure_; + +bool socket_address_needs_mount(const SocketAddress *a, const char *prefix); + +const char* socket_address_bind_ipv6_only_to_string(SocketAddressBindIPv6Only b) _const_; +SocketAddressBindIPv6Only socket_address_bind_ipv6_only_from_string(const char *s) _pure_; + +int netlink_family_to_string_alloc(int b, char **s); +int netlink_family_from_string(const char *s); + +bool socket_ipv6_is_supported(void); diff --git a/src/shared/sparse-endian.h b/src/shared/sparse-endian.h new file mode 100644 index 0000000000..eb4dbf3615 --- /dev/null +++ b/src/shared/sparse-endian.h @@ -0,0 +1,87 @@ +/* Copyright (c) 2012 Josh Triplett <josh@joshtriplett.org> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ +#ifndef SPARSE_ENDIAN_H +#define SPARSE_ENDIAN_H + +#include <endian.h> +#include <stdint.h> + +#ifdef __CHECKER__ +#define __bitwise __attribute__((bitwise)) +#define __force __attribute__((force)) +#else +#define __bitwise +#define __force +#endif + +typedef uint16_t __bitwise le16_t; +typedef uint16_t __bitwise be16_t; +typedef uint32_t __bitwise le32_t; +typedef uint32_t __bitwise be32_t; +typedef uint64_t __bitwise le64_t; +typedef uint64_t __bitwise be64_t; + +#undef htobe16 +#undef htole16 +#undef be16toh +#undef le16toh +#undef htobe32 +#undef htole32 +#undef be32toh +#undef le32toh +#undef htobe64 +#undef htole64 +#undef be64toh +#undef le64toh + +#if __BYTE_ORDER == __LITTLE_ENDIAN +#define bswap_16_on_le(x) __bswap_16(x) +#define bswap_32_on_le(x) __bswap_32(x) +#define bswap_64_on_le(x) __bswap_64(x) +#define bswap_16_on_be(x) (x) +#define bswap_32_on_be(x) (x) +#define bswap_64_on_be(x) (x) +#elif __BYTE_ORDER == __BIG_ENDIAN +#define bswap_16_on_le(x) (x) +#define bswap_32_on_le(x) (x) +#define bswap_64_on_le(x) (x) +#define bswap_16_on_be(x) __bswap_16(x) +#define bswap_32_on_be(x) __bswap_32(x) +#define bswap_64_on_be(x) __bswap_64(x) +#endif + +static inline le16_t htole16(uint16_t value) { return (le16_t __force) bswap_16_on_be(value); } +static inline le32_t htole32(uint32_t value) { return (le32_t __force) bswap_32_on_be(value); } +static inline le64_t htole64(uint64_t value) { return (le64_t __force) bswap_64_on_be(value); } + +static inline be16_t htobe16(uint16_t value) { return (be16_t __force) bswap_16_on_le(value); } +static inline be32_t htobe32(uint32_t value) { return (be32_t __force) bswap_32_on_le(value); } +static inline be64_t htobe64(uint64_t value) { return (be64_t __force) bswap_64_on_le(value); } + +static inline uint16_t le16toh(le16_t value) { return bswap_16_on_be((uint16_t __force)value); } +static inline uint32_t le32toh(le32_t value) { return bswap_32_on_be((uint32_t __force)value); } +static inline uint64_t le64toh(le64_t value) { return bswap_64_on_be((uint64_t __force)value); } + +static inline uint16_t be16toh(be16_t value) { return bswap_16_on_le((uint16_t __force)value); } +static inline uint32_t be32toh(be32_t value) { return bswap_32_on_le((uint32_t __force)value); } +static inline uint64_t be64toh(be64_t value) { return bswap_64_on_le((uint64_t __force)value); } + +#endif /* SPARSE_ENDIAN_H */ diff --git a/src/shared/spawn-ask-password-agent.c b/src/shared/spawn-ask-password-agent.c new file mode 100644 index 0000000000..c1a9c58681 --- /dev/null +++ b/src/shared/spawn-ask-password-agent.c @@ -0,0 +1,67 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <sys/types.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <sys/prctl.h> +#include <signal.h> +#include <fcntl.h> + +#include "log.h" +#include "util.h" +#include "spawn-ask-password-agent.h" + +static pid_t agent_pid = 0; + +int ask_password_agent_open(void) { + int r; + + if (agent_pid > 0) + return 0; + + /* We check STDIN here, not STDOUT, since this is about input, + * not output */ + if (!isatty(STDIN_FILENO)) + return 0; + + r = fork_agent(&agent_pid, + NULL, 0, + SYSTEMD_TTY_ASK_PASSWORD_AGENT_BINARY_PATH, + SYSTEMD_TTY_ASK_PASSWORD_AGENT_BINARY_PATH, "--watch", NULL); + if (r < 0) + log_error("Failed to fork TTY ask password agent: %s", strerror(-r)); + + return r; +} + +void ask_password_agent_close(void) { + + if (agent_pid <= 0) + return; + + /* Inform agent that we are done */ + kill(agent_pid, SIGTERM); + kill(agent_pid, SIGCONT); + wait_for_terminate(agent_pid, NULL); + agent_pid = 0; +} diff --git a/src/shared/spawn-ask-password-agent.h b/src/shared/spawn-ask-password-agent.h new file mode 100644 index 0000000000..31b4beab58 --- /dev/null +++ b/src/shared/spawn-ask-password-agent.h @@ -0,0 +1,25 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +int ask_password_agent_open(void); +void ask_password_agent_close(void); diff --git a/src/shared/spawn-polkit-agent.c b/src/shared/spawn-polkit-agent.c new file mode 100644 index 0000000000..f9e52cdcbd --- /dev/null +++ b/src/shared/spawn-polkit-agent.c @@ -0,0 +1,98 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <sys/types.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <sys/prctl.h> +#include <signal.h> +#include <fcntl.h> +#include <errno.h> +#include <sys/poll.h> + +#include "log.h" +#include "util.h" +#include "spawn-polkit-agent.h" + +#ifdef ENABLE_POLKIT +static pid_t agent_pid = 0; + +int polkit_agent_open(void) { + int r; + int pipe_fd[2]; + char notify_fd[10 + 1]; + + if (agent_pid > 0) + return 0; + + /* We check STDIN here, not STDOUT, since this is about input, + * not output */ + if (!isatty(STDIN_FILENO)) + return 0; + + if (pipe2(pipe_fd, 0) < 0) + return -errno; + + snprintf(notify_fd, sizeof(notify_fd), "%i", pipe_fd[1]); + char_array_0(notify_fd); + + r = fork_agent(&agent_pid, + &pipe_fd[1], 1, + POLKIT_AGENT_BINARY_PATH, + POLKIT_AGENT_BINARY_PATH, "--notify-fd", notify_fd, "--fallback", NULL); + + /* Close the writing side, because that's the one for the agent */ + close_nointr_nofail(pipe_fd[1]); + + if (r < 0) + log_error("Failed to fork TTY ask password agent: %s", strerror(-r)); + else + /* Wait until the agent closes the fd */ + fd_wait_for_event(pipe_fd[0], POLLHUP, (usec_t) -1); + + close_nointr_nofail(pipe_fd[0]); + + return r; +} + +void polkit_agent_close(void) { + + if (agent_pid <= 0) + return; + + /* Inform agent that we are done */ + kill(agent_pid, SIGTERM); + kill(agent_pid, SIGCONT); + wait_for_terminate(agent_pid, NULL); + agent_pid = 0; +} + +#else + +int polkit_agent_open(void) { + return 0; +} + +void polkit_agent_close(void) { +} + +#endif diff --git a/src/shared/spawn-polkit-agent.h b/src/shared/spawn-polkit-agent.h new file mode 100644 index 0000000000..b91d20f120 --- /dev/null +++ b/src/shared/spawn-polkit-agent.h @@ -0,0 +1,28 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foospawnpolkitagenthfoo +#define foospawnpolkitagenthfoo + +/*** + This file is part of systemd. + + Copyright 2012 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +int polkit_agent_open(void); +void polkit_agent_close(void); + +#endif diff --git a/src/shared/specifier.c b/src/shared/specifier.c new file mode 100644 index 0000000000..7577c91052 --- /dev/null +++ b/src/shared/specifier.c @@ -0,0 +1,147 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <string.h> + +#include "macro.h" +#include "util.h" +#include "specifier.h" + +/* + * Generic infrastructure for replacing %x style specifiers in + * strings. Will call a callback for each replacement. + * + */ + +char *specifier_printf(const char *text, const Specifier table[], void *userdata) { + char *r, *t; + const char *f; + bool percent = false; + size_t l; + + assert(text); + assert(table); + + l = strlen(text); + r = new(char, l+1); + if (!r) + return NULL; + + t = r; + + for (f = text; *f; f++, l--) { + + if (percent) { + if (*f == '%') + *(t++) = '%'; + else { + const Specifier *i; + + for (i = table; i->specifier; i++) + if (i->specifier == *f) + break; + + if (i->lookup) { + char *n, *w; + size_t k, j; + + w = i->lookup(i->specifier, i->data, userdata); + if (!w) { + free(r); + return NULL; + } + + j = t - r; + k = strlen(w); + + n = new(char, j + k + l + 1); + if (!n) { + free(r); + free(w); + return NULL; + } + + memcpy(n, r, j); + memcpy(n + j, w, k); + + free(r); + free(w); + + r = n; + t = n + j + k; + } else { + *(t++) = '%'; + *(t++) = *f; + } + } + + percent = false; + } else if (*f == '%') + percent = true; + else + *(t++) = *f; + } + + *t = 0; + return r; +} + +/* Generic handler for simple string replacements */ + +char* specifier_string(char specifier, void *data, void *userdata) { + return strdup(strempty(data)); +} + +char *specifier_machine_id(char specifier, void *data, void *userdata) { + sd_id128_t id; + char *buf; + int r; + + r = sd_id128_get_machine(&id); + if (r < 0) + return NULL; + + buf = new(char, 33); + if (!buf) + return NULL; + + return sd_id128_to_string(id, buf); +} + +char *specifier_boot_id(char specifier, void *data, void *userdata) { + sd_id128_t id; + char *buf; + int r; + + r = sd_id128_get_boot(&id); + if (r < 0) + return NULL; + + buf = new(char, 33); + if (!buf) + return NULL; + + return sd_id128_to_string(id, buf); +} + +char *specifier_host_name(char specifier, void *data, void *userdata) { + return gethostname_malloc(); +} diff --git a/src/shared/specifier.h b/src/shared/specifier.h new file mode 100644 index 0000000000..0440dcac48 --- /dev/null +++ b/src/shared/specifier.h @@ -0,0 +1,38 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +typedef char* (*SpecifierCallback)(char specifier, void *data, void *userdata); + +typedef struct Specifier { + const char specifier; + const SpecifierCallback lookup; + void *data; +} Specifier; + +char *specifier_printf(const char *text, const Specifier table[], void *userdata); + +char *specifier_string(char specifier, void *data, void *userdata); + +char *specifier_machine_id(char specifier, void *data, void *userdata); +char *specifier_boot_id(char specifier, void *data, void *userdata); +char *specifier_host_name(char specifier, void *data, void *userdata); diff --git a/src/shared/strbuf.c b/src/shared/strbuf.c new file mode 100644 index 0000000000..01a076c2ba --- /dev/null +++ b/src/shared/strbuf.c @@ -0,0 +1,201 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2012 Kay Sievers <kay@vrfy.org> + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <stdlib.h> +#include <string.h> + +#include "util.h" +#include "strbuf.h" + +/* + * Strbuf stores given strings in a single continuous allocated memory + * area. Identical strings are de-duplicated and return the same offset + * as the first string stored. If the tail of a string already exists + * in the buffer, the tail is returned. + * + * A trie (http://en.wikipedia.org/wiki/Trie) is used to maintain the + * information about the stored strings. + * + * Example of udev rules: + * $ ./udevadm test . + * ... + * read rules file: /usr/lib/udev/rules.d/99-systemd.rules + * rules contain 196608 bytes tokens (16384 * 12 bytes), 39742 bytes strings + * 23939 strings (207859 bytes), 20404 de-duplicated (171653 bytes), 3536 trie nodes used + * ... + */ + +struct strbuf *strbuf_new(void) { + struct strbuf *str; + + str = new0(struct strbuf, 1); + if (!str) + return NULL; + + str->buf = new0(char, 1); + if (!str->buf) + goto err; + str->len = 1; + + str->root = new0(struct strbuf_node, 1); + if (!str->root) + goto err; + str->nodes_count = 1; + return str; +err: + free(str->buf); + free(str->root); + free(str); + return NULL; +} + +static void strbuf_node_cleanup(struct strbuf_node *node) { + size_t i; + + for (i = 0; i < node->children_count; i++) + strbuf_node_cleanup(node->children[i].child); + free(node->children); + free(node); +} + +/* clean up trie data, leave only the string buffer */ +void strbuf_complete(struct strbuf *str) { + if (!str) + return; + if (str->root) + strbuf_node_cleanup(str->root); + str->root = NULL; +} + +/* clean up everything */ +void strbuf_cleanup(struct strbuf *str) { + if (!str) + return; + if (str->root) + strbuf_node_cleanup(str->root); + free(str->buf); + free(str); +} + +static int strbuf_children_cmp(const struct strbuf_child_entry *n1, + const struct strbuf_child_entry *n2) { + return n1->c - n2->c; +} + +static void bubbleinsert(struct strbuf_node *node, + uint8_t c, + struct strbuf_node *node_child) { + + struct strbuf_child_entry new = { + .c = c, + .child = node_child, + }; + int left = 0, right = node->children_count; + + while (right > left) { + int middle = (right + left) / 2 ; + if (strbuf_children_cmp(&node->children[middle], &new) <= 0) + left = middle + 1; + else + right = middle; + } + + memmove(node->children + left + 1, node->children + left, + sizeof(struct strbuf_child_entry) * (node->children_count - left)); + node->children[left] = new; + + node->children_count ++; +} + +/* add string, return the index/offset into the buffer */ +ssize_t strbuf_add_string(struct strbuf *str, const char *s, size_t len) { + uint8_t c; + struct strbuf_node *node; + size_t depth; + char *buf_new; + struct strbuf_child_entry *child; + struct strbuf_node *node_child; + ssize_t off; + + if (!str->root) + return -EINVAL; + + /* search string; start from last character to find possibly matching tails */ + if (len == 0) + return 0; + str->in_count++; + str->in_len += len; + + node = str->root; + c = s[len-1]; + for (depth = 0; depth <= len; depth++) { + struct strbuf_child_entry search; + + /* match against current node */ + off = node->value_off + node->value_len - len; + if (depth == len || (node->value_len >= len && memcmp(str->buf + off, s, len) == 0)) { + str->dedup_len += len; + str->dedup_count++; + return off; + } + + /* lookup child node */ + c = s[len - 1 - depth]; + search.c = c; + child = bsearch(&search, node->children, node->children_count, + sizeof(struct strbuf_child_entry), + (__compar_fn_t) strbuf_children_cmp); + if (!child) + break; + node = child->child; + } + + /* add new string */ + buf_new = realloc(str->buf, str->len + len+1); + if (!buf_new) + return -ENOMEM; + str->buf = buf_new; + off = str->len; + memcpy(str->buf + off, s, len); + str->len += len; + str->buf[str->len++] = '\0'; + + /* new node */ + node_child = new0(struct strbuf_node, 1); + if (!node_child) + return -ENOMEM; + node_child->value_off = off; + node_child->value_len = len; + + /* extend array, add new entry, sort for bisection */ + child = realloc(node->children, (node->children_count + 1) * sizeof(struct strbuf_child_entry)); + if (!child) { + free(node_child); + return -ENOMEM; + } + + str->nodes_count++; + + node->children = child; + bubbleinsert(node, c, node_child); + + return off; +} diff --git a/src/shared/strbuf.h b/src/shared/strbuf.h new file mode 100644 index 0000000000..2347fd4328 --- /dev/null +++ b/src/shared/strbuf.h @@ -0,0 +1,56 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2012 Kay Sievers <kay@vrfy.org> + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <stdarg.h> +#include <stdint.h> +#include <stdbool.h> + +struct strbuf { + char *buf; + size_t len; + struct strbuf_node *root; + + size_t nodes_count; + size_t in_count; + size_t in_len; + size_t dedup_len; + size_t dedup_count; +}; + +struct strbuf_node { + size_t value_off; + size_t value_len; + + struct strbuf_child_entry *children; + uint8_t children_count; +}; + +struct strbuf_child_entry { + uint8_t c; + struct strbuf_node *child; +}; + +struct strbuf *strbuf_new(void); +ssize_t strbuf_add_string(struct strbuf *str, const char *s, size_t len); +void strbuf_complete(struct strbuf *str); +void strbuf_cleanup(struct strbuf *str); diff --git a/src/shared/strv.c b/src/shared/strv.c new file mode 100644 index 0000000000..a5ce7e9593 --- /dev/null +++ b/src/shared/strv.c @@ -0,0 +1,587 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <assert.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <errno.h> + +#include "util.h" +#include "strv.h" + +char *strv_find(char **l, const char *name) { + char **i; + + assert(name); + + STRV_FOREACH(i, l) + if (streq(*i, name)) + return *i; + + return NULL; +} + +char *strv_find_prefix(char **l, const char *name) { + char **i; + + assert(name); + + STRV_FOREACH(i, l) + if (startswith(*i, name)) + return *i; + + return NULL; +} + +void strv_free(char **l) { + char **k; + + if (!l) + return; + + for (k = l; *k; k++) + free(*k); + + free(l); +} + +char **strv_copy(char * const *l) { + char **r, **k; + + k = r = new(char*, strv_length(l) + 1); + if (!r) + return NULL; + + if (l) + for (; *l; k++, l++) { + *k = strdup(*l); + if (!*k) { + strv_free(r); + return NULL; + } + } + + *k = NULL; + return r; +} + +unsigned strv_length(char * const *l) { + unsigned n = 0; + + if (!l) + return 0; + + for (; *l; l++) + n++; + + return n; +} + +char **strv_new_ap(const char *x, va_list ap) { + const char *s; + char **a; + unsigned n = 0, i = 0; + va_list aq; + + /* As a special trick we ignore all listed strings that equal + * (const char*) -1. This is supposed to be used with the + * STRV_IFNOTNULL() macro to include possibly NULL strings in + * the string list. */ + + if (x) { + n = x == (const char*) -1 ? 0 : 1; + + va_copy(aq, ap); + while ((s = va_arg(aq, const char*))) { + if (s == (const char*) -1) + continue; + + n++; + } + + va_end(aq); + } + + a = new(char*, n+1); + if (!a) + return NULL; + + if (x) { + if (x != (const char*) -1) { + a[i] = strdup(x); + if (!a[i]) + goto fail; + i++; + } + + while ((s = va_arg(ap, const char*))) { + + if (s == (const char*) -1) + continue; + + a[i] = strdup(s); + if (!a[i]) + goto fail; + + i++; + } + } + + a[i] = NULL; + + return a; + +fail: + strv_free(a); + return NULL; +} + +char **strv_new(const char *x, ...) { + char **r; + va_list ap; + + va_start(ap, x); + r = strv_new_ap(x, ap); + va_end(ap); + + return r; +} + +char **strv_merge(char **a, char **b) { + char **r, **k; + + if (!a) + return strv_copy(b); + + if (!b) + return strv_copy(a); + + r = new(char*, strv_length(a) + strv_length(b) + 1); + if (!r) + return NULL; + + for (k = r; *a; k++, a++) { + *k = strdup(*a); + if (!*k) + goto fail; + } + + for (; *b; k++, b++) { + *k = strdup(*b); + if (!*k) + goto fail; + } + + *k = NULL; + return r; + +fail: + strv_free(r); + return NULL; +} + +char **strv_merge_concat(char **a, char **b, const char *suffix) { + char **r, **k; + + /* Like strv_merge(), but appends suffix to all strings in b, before adding */ + + if (!b) + return strv_copy(a); + + r = new(char*, strv_length(a) + strv_length(b) + 1); + if (!r) + return NULL; + + k = r; + if (a) + for (; *a; k++, a++) { + *k = strdup(*a); + if (!*k) + goto fail; + } + + for (; *b; k++, b++) { + *k = strappend(*b, suffix); + if (!*k) + goto fail; + } + + *k = NULL; + return r; + +fail: + strv_free(r); + return NULL; + +} + +char **strv_split(const char *s, const char *separator) { + char *state; + char *w; + size_t l; + unsigned n, i; + char **r; + + assert(s); + + n = 0; + FOREACH_WORD_SEPARATOR(w, l, s, separator, state) + n++; + + r = new(char*, n+1); + if (!r) + return NULL; + + i = 0; + FOREACH_WORD_SEPARATOR(w, l, s, separator, state) { + r[i] = strndup(w, l); + if (!r[i]) { + strv_free(r); + return NULL; + } + + i++; + } + + r[i] = NULL; + return r; +} + +char **strv_split_quoted(const char *s) { + char *state; + char *w; + size_t l; + unsigned n, i; + char **r; + + assert(s); + + n = 0; + FOREACH_WORD_QUOTED(w, l, s, state) + n++; + + r = new(char*, n+1); + if (!r) + return NULL; + + i = 0; + FOREACH_WORD_QUOTED(w, l, s, state) { + r[i] = cunescape_length(w, l); + if (!r[i]) { + strv_free(r); + return NULL; + } + i++; + } + + r[i] = NULL; + return r; +} + +char **strv_split_newlines(const char *s) { + char **l; + unsigned n; + + assert(s); + + /* Special version of strv_split() that splits on newlines and + * suppresses an empty string at the end */ + + l = strv_split(s, NEWLINE); + if (!l) + return NULL; + + n = strv_length(l); + if (n <= 0) + return l; + + if (isempty(l[n-1])) { + free(l[n-1]); + l[n-1] = NULL; + } + + return l; +} + +char *strv_join(char **l, const char *separator) { + char *r, *e; + char **s; + size_t n, k; + + if (!separator) + separator = " "; + + k = strlen(separator); + + n = 0; + STRV_FOREACH(s, l) { + if (n != 0) + n += k; + n += strlen(*s); + } + + r = new(char, n+1); + if (!r) + return NULL; + + e = r; + STRV_FOREACH(s, l) { + if (e != r) + e = stpcpy(e, separator); + + e = stpcpy(e, *s); + } + + *e = 0; + + return r; +} + +char **strv_append(char **l, const char *s) { + char **r, **k; + + if (!l) + return strv_new(s, NULL); + + if (!s) + return strv_copy(l); + + r = new(char*, strv_length(l)+2); + if (!r) + return NULL; + + for (k = r; *l; k++, l++) { + *k = strdup(*l); + if (!*k) + goto fail; + } + + k[0] = strdup(s); + if (!k[0]) + goto fail; + + k[1] = NULL; + return r; + +fail: + strv_free(r); + return NULL; +} + +int strv_push(char ***l, char *value) { + char **c; + unsigned n; + + if (!value) + return 0; + + n = strv_length(*l); + c = realloc(*l, sizeof(char*) * (n + 2)); + if (!c) + return -ENOMEM; + + c[n] = value; + c[n+1] = NULL; + + *l = c; + return 0; +} + +int strv_extend(char ***l, const char *value) { + char *v; + int r; + + if (!value) + return 0; + + v = strdup(value); + if (!v) + return -ENOMEM; + + r = strv_push(l, v); + if (r < 0) + free(v); + + return r; +} + +char **strv_uniq(char **l) { + char **i; + + /* Drops duplicate entries. The first identical string will be + * kept, the others dropped */ + + STRV_FOREACH(i, l) + strv_remove(i+1, *i); + + return l; +} + +char **strv_remove(char **l, const char *s) { + char **f, **t; + + if (!l) + return NULL; + + assert(s); + + /* Drops every occurrence of s in the string list, edits + * in-place. */ + + for (f = t = l; *f; f++) { + + if (streq(*f, s)) { + free(*f); + continue; + } + + *(t++) = *f; + } + + *t = NULL; + return l; +} + +char **strv_remove_prefix(char **l, const char *s) { + char **f, **t; + + if (!l) + return NULL; + + assert(s); + + /* Drops every occurrence of a string prefixed with s in the + * string list, edits in-place. */ + + for (f = t = l; *f; f++) { + + if (startswith(*f, s)) { + free(*f); + continue; + } + + *(t++) = *f; + } + + *t = NULL; + return l; +} + +char **strv_parse_nulstr(const char *s, size_t l) { + const char *p; + unsigned c = 0, i = 0; + char **v; + + assert(s || l <= 0); + + if (l <= 0) + return strv_new(NULL, NULL); + + for (p = s; p < s + l; p++) + if (*p == 0) + c++; + + if (s[l-1] != 0) + c++; + + v = new0(char*, c+1); + if (!v) + return NULL; + + p = s; + while (p < s + l) { + const char *e; + + e = memchr(p, 0, s + l - p); + + v[i] = strndup(p, e ? e - p : s + l - p); + if (!v[i]) { + strv_free(v); + return NULL; + } + + i++; + + if (!e) + break; + + p = e + 1; + } + + assert(i == c); + + return v; +} + +char **strv_split_nulstr(const char *s) { + const char *i; + char **r = NULL; + + NULSTR_FOREACH(i, s) + if (strv_extend(&r, i) < 0) { + strv_free(r); + return NULL; + } + + if (!r) + return strv_new(NULL, NULL); + + return r; +} + +bool strv_overlap(char **a, char **b) { + char **i, **j; + + STRV_FOREACH(i, a) { + STRV_FOREACH(j, b) { + if (streq(*i, *j)) + return true; + } + } + + return false; +} + +static int str_compare(const void *_a, const void *_b) { + const char **a = (const char**) _a, **b = (const char**) _b; + + return strcmp(*a, *b); +} + +char **strv_sort(char **l) { + + if (strv_isempty(l)) + return l; + + qsort(l, strv_length(l), sizeof(char*), str_compare); + return l; +} + +void strv_print(char **l) { + char **s; + + if (!l) + return; + + STRV_FOREACH(s, l) + puts(*s); +} diff --git a/src/shared/strv.h b/src/shared/strv.h new file mode 100644 index 0000000000..e35118752f --- /dev/null +++ b/src/shared/strv.h @@ -0,0 +1,87 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <stdarg.h> +#include <stdbool.h> + +#include "macro.h" + +char *strv_find(char **l, const char *name) _pure_; +char *strv_find_prefix(char **l, const char *name) _pure_; + +void strv_free(char **l); +static inline void strv_freep(char ***l) { + strv_free(*l); +} + +#define _cleanup_strv_free_ _cleanup_(strv_freep) + +char **strv_copy(char * const *l); +unsigned strv_length(char * const *l) _pure_; + +char **strv_merge(char **a, char **b); +char **strv_merge_concat(char **a, char **b, const char *suffix); +char **strv_append(char **l, const char *s); +int strv_extend(char ***l, const char *value); +int strv_push(char ***l, char *value); + +char **strv_remove(char **l, const char *s); +char **strv_remove_prefix(char **l, const char *s); +char **strv_uniq(char **l); + +#define strv_contains(l, s) (!!strv_find((l), (s))) + +char **strv_new(const char *x, ...) _sentinel_; +char **strv_new_ap(const char *x, va_list ap); + +static inline const char* STRV_IFNOTNULL(const char *x) { + return x ? x : (const char *) -1; +} + +static inline bool strv_isempty(char * const *l) { + return !l || !*l; +} + +char **strv_split(const char *s, const char *separator); +char **strv_split_quoted(const char *s); +char **strv_split_newlines(const char *s); + +char *strv_join(char **l, const char *separator); + +char **strv_parse_nulstr(const char *s, size_t l); +char **strv_split_nulstr(const char *s); + +bool strv_overlap(char **a, char **b) _pure_; + +#define STRV_FOREACH(s, l) \ + for ((s) = (l); (s) && *(s); (s)++) + +#define STRV_FOREACH_BACKWARDS(s, l) \ + for (; (l) && ((s) >= (l)); (s)--) + +#define STRV_FOREACH_PAIR(x, y, l) \ + for ((x) = (l), (y) = (x+1); (x) && *(x) && *(y); (x) += 2, (y) = (x + 1)) + + +char **strv_sort(char **l); +void strv_print(char **l); diff --git a/src/shared/strxcpyx.c b/src/shared/strxcpyx.c new file mode 100644 index 0000000000..3cb04b25cd --- /dev/null +++ b/src/shared/strxcpyx.c @@ -0,0 +1,104 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2013 Kay Sievers + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +/* + * Concatenates/copies strings. In any case, terminates in all cases + * with '\0' * and moves the @dest pointer forward to the added '\0'. + * Returns the * remaining size, and 0 if the string was truncated. + */ + +#include <stdio.h> +#include <string.h> +#include "strxcpyx.h" + +size_t strpcpy(char **dest, size_t size, const char *src) +{ + size_t len; + + len = strlen(src); + if (len >= size) { + if (size > 1) + *dest = mempcpy(*dest, src, size-1); + size = 0; + } else { + if (len > 0) { + *dest = mempcpy(*dest, src, len); + size -= len; + } + } + *dest[0] = '\0'; + return size; +} + +size_t strpcpyf(char **dest, size_t size, const char *src, ...) +{ + va_list va; + int i; + + va_start(va, src); + i = vsnprintf(*dest, size, src, va); + if (i < (int)size) { + *dest += i; + size -= i; + } else { + *dest += size; + size = 0; + } + va_end(va); + *dest[0] = '\0'; + return size; +} + +size_t strpcpyl(char **dest, size_t size, const char *src, ...) +{ + va_list va; + + va_start(va, src); + do { + size = strpcpy(dest, size, src); + src = va_arg(va, char *); + } while (src != NULL); + va_end(va); + return size; +} + +size_t strscpy(char *dest, size_t size, const char *src) +{ + char *s; + + s = dest; + return strpcpy(&s, size, src); +} + +size_t strscpyl(char *dest, size_t size, const char *src, ...) { + va_list va; + char *s; + + va_start(va, src); + s = dest; + do { + size = strpcpy(&s, size, src); + src = va_arg(va, char *); + } while (src != NULL); + va_end(va); + + return size; +} diff --git a/src/shared/strxcpyx.h b/src/shared/strxcpyx.h new file mode 100644 index 0000000000..1229a4821d --- /dev/null +++ b/src/shared/strxcpyx.h @@ -0,0 +1,31 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 Kay Sievers + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <stdarg.h> +#include <stdbool.h> + +size_t strpcpy(char **dest, size_t size, const char *src); +size_t strpcpyf(char **dest, size_t size, const char *src, ...) __attribute__((format(printf, 3, 4))); +size_t strpcpyl(char **dest, size_t size, const char *src, ...) __attribute__((sentinel)); +size_t strscpy(char *dest, size_t size, const char *src); +size_t strscpyl(char *dest, size_t size, const char *src, ...) __attribute__((sentinel)); diff --git a/src/shared/time-dst.c b/src/shared/time-dst.c new file mode 100644 index 0000000000..3950a882bf --- /dev/null +++ b/src/shared/time-dst.c @@ -0,0 +1,332 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Timezone file reading code from glibc 2.16. + + Copyright (C) 1991-2012 Free Software Foundation, Inc. + Copyright 2012 Kay Sievers + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ +#include <ctype.h> +#include <errno.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <endian.h> +#include <byteswap.h> +#include <assert.h> +#include <limits.h> +#include <unistd.h> +#include <stdint.h> +#include <stdbool.h> +#include <sys/stat.h> + +#include "time-dst.h" +#include "util.h" + +/* + * If tzh_version is '2' or greater, the above is followed by a second instance + * of tzhead and a second instance of the data in which each coded transition + * time uses 8 rather than 4 chars, then a POSIX-TZ-environment-variable-style + * string for use in handling instants after the last transition time stored in + * the file * (with nothing between the newlines if there is no POSIX + * representation for such instants). + */ +#define TZ_MAGIC "TZif" +struct tzhead { + char tzh_magic[4]; /* TZ_MAGIC */ + char tzh_version[1]; /* '\0' or '2' as of 2005 */ + char tzh_reserved[15]; /* reserved--must be zero */ + char tzh_ttisgmtcnt[4]; /* coded number of trans. time flags */ + char tzh_ttisstdcnt[4]; /* coded number of trans. time flags */ + char tzh_leapcnt[4]; /* coded number of leap seconds */ + char tzh_timecnt[4]; /* coded number of transition times */ + char tzh_typecnt[4]; /* coded number of local time types */ + char tzh_charcnt[4]; /* coded number of abbr. chars */ +}; + +struct ttinfo { + long int offset; /* Seconds east of GMT. */ + unsigned char isdst; /* Used to set tm_isdst. */ + unsigned char idx; /* Index into `zone_names'. */ + unsigned char isstd; /* Transition times are in standard time. */ + unsigned char isgmt; /* Transition times are in GMT. */ +}; + +struct leap { + time_t transition; /* Time the transition takes effect. */ + long int change; /* Seconds of correction to apply. */ +}; + +static inline int decode(const void *ptr) { + return be32toh(*(int *)ptr); +} + +static inline int64_t decode64(const void *ptr) { + return be64toh(*(int64_t *)ptr); +} + +int time_get_dst(time_t date, const char *tzfile, + time_t *switch_cur, char **zone_cur, bool *dst_cur, + time_t *switch_next, int *delta_next, char **zone_next, bool *dst_next) { + unsigned char *type_idxs = 0; + size_t num_types = 0; + struct ttinfo *types = NULL; + char *zone_names = NULL; + struct stat st; + size_t num_isstd, num_isgmt; + struct tzhead tzhead; + size_t chars; + size_t i; + size_t total_size; + size_t types_idx; + int trans_width = 4; + size_t tzspec_len; + size_t num_leaps; + size_t lo, hi; + size_t num_transitions = 0; + _cleanup_free_ time_t *transitions = NULL; + _cleanup_fclose_ FILE *f; + + f = fopen(tzfile, "re"); + if (f == NULL) + return -errno; + + if (fstat(fileno(f), &st) < 0) + return -errno; + +read_again: + if (fread((void *)&tzhead, sizeof(tzhead), 1, f) != 1 || + memcmp(tzhead.tzh_magic, TZ_MAGIC, sizeof(tzhead.tzh_magic)) != 0) + return -EINVAL; + + num_transitions = (size_t)decode(tzhead.tzh_timecnt); + num_types = (size_t)decode(tzhead.tzh_typecnt); + chars = (size_t)decode(tzhead.tzh_charcnt); + num_leaps = (size_t)decode(tzhead.tzh_leapcnt); + num_isstd = (size_t)decode(tzhead.tzh_ttisstdcnt); + num_isgmt = (size_t)decode(tzhead.tzh_ttisgmtcnt); + + /* For platforms with 64-bit time_t we use the new format if available. */ + if (sizeof(time_t) == 8 && trans_width == 4 && tzhead.tzh_version[0] != '\0') { + size_t to_skip; + + /* We use the 8-byte format. */ + trans_width = 8; + + /* Position the stream before the second header. */ + to_skip = (num_transitions * (4 + 1) + + num_types * 6 + + chars + + num_leaps * 8 + num_isstd + num_isgmt); + if (fseek(f, to_skip, SEEK_CUR) != 0) + return -EINVAL; + + goto read_again; + } + + if (num_transitions > ((SIZE_MAX - (__alignof__(struct ttinfo) - 1)) / (sizeof(time_t) + 1))) + return -EINVAL; + + total_size = num_transitions * (sizeof(time_t) + 1); + total_size = ((total_size + __alignof__(struct ttinfo) - 1) & ~(__alignof__(struct ttinfo) - 1)); + types_idx = total_size; + if (num_leaps > (SIZE_MAX - total_size) / sizeof(struct ttinfo)) + return -EINVAL; + + total_size += num_types * sizeof(struct ttinfo); + if (chars > SIZE_MAX - total_size) + return -EINVAL; + + total_size += chars; + if (__alignof__(struct leap) - 1 > SIZE_MAX - total_size) + return -EINVAL; + + total_size = ((total_size + __alignof__(struct leap) - 1) & ~(__alignof__(struct leap) - 1)); + if (num_leaps > (SIZE_MAX - total_size) / sizeof(struct leap)) + return -EINVAL; + + total_size += num_leaps * sizeof(struct leap); + tzspec_len = 0; + if (sizeof(time_t) == 8 && trans_width == 8) { + off_t rem = st.st_size - ftello(f); + + if (rem < 0 || (size_t) rem < (num_transitions * (8 + 1) + num_types * 6 + chars)) + return -EINVAL; + tzspec_len = (size_t) rem - (num_transitions * (8 + 1) + num_types * 6 + chars); + if (num_leaps > SIZE_MAX / 12 || tzspec_len < num_leaps * 12) + return -EINVAL; + tzspec_len -= num_leaps * 12; + if (tzspec_len < num_isstd) + return -EINVAL; + tzspec_len -= num_isstd; + if (tzspec_len == 0 || tzspec_len - 1 < num_isgmt) + return -EINVAL; + tzspec_len -= num_isgmt + 1; + if (SIZE_MAX - total_size < tzspec_len) + return -EINVAL; + } + + transitions = (time_t *)calloc(total_size + tzspec_len, 1); + if (transitions == NULL) + return -EINVAL; + + type_idxs = (unsigned char *)transitions + (num_transitions + * sizeof(time_t)); + types = (struct ttinfo *)((char *)transitions + types_idx); + zone_names = (char *)types + num_types * sizeof(struct ttinfo); + + if (sizeof(time_t) == 4 || trans_width == 8) { + if (fread(transitions, trans_width + 1, num_transitions, f) != num_transitions) + return -EINVAL; + } else { + if (fread(transitions, 4, num_transitions, f) != num_transitions || + fread(type_idxs, 1, num_transitions, f) != num_transitions) + return -EINVAL; + } + + /* Check for bogus indices in the data file, so we can hereafter + safely use type_idxs[T] as indices into `types' and never crash. */ + for (i = 0; i < num_transitions; ++i) + if (type_idxs[i] >= num_types) + return -EINVAL; + + if ((BYTE_ORDER != BIG_ENDIAN && (sizeof(time_t) == 4 || trans_width == 4)) || + (BYTE_ORDER == BIG_ENDIAN && sizeof(time_t) == 8 && trans_width == 4)) { + /* Decode the transition times, stored as 4-byte integers in + network (big-endian) byte order. We work from the end of + the array so as not to clobber the next element to be + processed when sizeof (time_t) > 4. */ + i = num_transitions; + while (i-- > 0) + transitions[i] = decode((char *)transitions + i * 4); + } else if (BYTE_ORDER != BIG_ENDIAN && sizeof(time_t) == 8) { + /* Decode the transition times, stored as 8-byte integers in + network (big-endian) byte order. */ + for (i = 0; i < num_transitions; ++i) + transitions[i] = decode64((char *)transitions + i * 8); + } + + for (i = 0; i < num_types; ++i) { + unsigned char x[4]; + int c; + + if (fread(x, 1, sizeof(x), f) != sizeof(x)) + return -EINVAL; + c = getc(f); + if ((unsigned int)c > 1u) + return -EINVAL; + types[i].isdst = c; + c = getc(f); + if ((size_t) c > chars) + /* Bogus index in data file. */ + return -EINVAL; + types[i].idx = c; + types[i].offset = (long int)decode(x); + } + + if (fread(zone_names, 1, chars, f) != chars) + return -EINVAL; + + for (i = 0; i < num_isstd; ++i) { + int c = getc(f); + if (c == EOF) + return -EINVAL; + types[i].isstd = c != 0; + } + + while (i < num_types) + types[i++].isstd = 0; + + for (i = 0; i < num_isgmt; ++i) { + int c = getc(f); + if (c == EOF) + return -EINVAL; + types[i].isgmt = c != 0; + } + + while (i < num_types) + types[i++].isgmt = 0; + + if (num_transitions == 0) + return -EINVAL; + + if (date < transitions[0] || date >= transitions[num_transitions - 1]) + return -EINVAL; + + /* Find the first transition after TIMER, and + then pick the type of the transition before it. */ + lo = 0; + hi = num_transitions - 1; + + /* Assume that DST is changing twice a year and guess initial + search spot from it. + Half of a gregorian year has on average 365.2425 * 86400 / 2 + = 15778476 seconds. */ + i = (transitions[num_transitions - 1] - date) / 15778476; + if (i < num_transitions) { + i = num_transitions - 1 - i; + if (date < transitions[i]) { + if (i < 10 || date >= transitions[i - 10]) { + /* Linear search. */ + while (date < transitions[i - 1]) + i--; + goto found; + } + hi = i - 10; + } else { + if (i + 10 >= num_transitions || date < transitions[i + 10]) { + /* Linear search. */ + while (date >= transitions[i]) + i++; + goto found; + } + lo = i + 10; + } + } + + /* Binary search. */ + while (lo + 1 < hi) { + i = (lo + hi) / 2; + if (date < transitions[i]) + hi = i; + else + lo = i; + } + i = hi; + +found: + if (switch_cur) + *switch_cur = transitions[i-1]; + if (zone_cur) + *zone_cur = strdup(&zone_names[types[type_idxs[i - 1]].idx]); + if (dst_cur) + *dst_cur = types[type_idxs[i-1]].isdst; + + if (switch_next) + *switch_next = transitions[i]; + if (delta_next) + *delta_next = (types[type_idxs[i]].offset - types[type_idxs[i-1]].offset) / 60; + if (zone_next) + *zone_next = strdup(&zone_names[types[type_idxs[i]].idx]); + if (dst_next) + *dst_next = types[type_idxs[i]].isdst; + + return 0; +} diff --git a/src/shared/time-dst.h b/src/shared/time-dst.h new file mode 100644 index 0000000000..536b6bbdfb --- /dev/null +++ b/src/shared/time-dst.h @@ -0,0 +1,26 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2012 Kay Sievers + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +int time_get_dst(time_t date, const char *tzfile, + time_t *switch_cur, char **zone_cur, bool *dst_cur, + time_t *switch_next, int *delta_next, char **zone_next, bool *dst_next); diff --git a/src/shared/time-util.c b/src/shared/time-util.c new file mode 100644 index 0000000000..9ee711a49e --- /dev/null +++ b/src/shared/time-util.c @@ -0,0 +1,772 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <time.h> +#include <string.h> + +#include "util.h" +#include "time-util.h" + +usec_t now(clockid_t clock_id) { + struct timespec ts; + + assert_se(clock_gettime(clock_id, &ts) == 0); + + return timespec_load(&ts); +} + +dual_timestamp* dual_timestamp_get(dual_timestamp *ts) { + assert(ts); + + ts->realtime = now(CLOCK_REALTIME); + ts->monotonic = now(CLOCK_MONOTONIC); + + return ts; +} + +dual_timestamp* dual_timestamp_from_realtime(dual_timestamp *ts, usec_t u) { + int64_t delta; + assert(ts); + + if (u == (usec_t) -1) { + ts->realtime = ts->monotonic = (usec_t) -1; + return ts; + } + + ts->realtime = u; + + if (u == 0) + ts->monotonic = 0; + else { + delta = (int64_t) now(CLOCK_REALTIME) - (int64_t) u; + + ts->monotonic = now(CLOCK_MONOTONIC); + + if ((int64_t) ts->monotonic > delta) + ts->monotonic -= delta; + else + ts->monotonic = 0; + } + + return ts; +} + +dual_timestamp* dual_timestamp_from_monotonic(dual_timestamp *ts, usec_t u) { + int64_t delta; + assert(ts); + + if (u == (usec_t) -1) { + ts->realtime = ts->monotonic = (usec_t) -1; + return ts; + } + + ts->monotonic = u; + delta = (int64_t) now(CLOCK_MONOTONIC) - (int64_t) u; + + ts->realtime = now(CLOCK_REALTIME); + if ((int64_t) ts->realtime > delta) + ts->realtime -= delta; + else + ts->realtime = 0; + + return ts; +} + +usec_t timespec_load(const struct timespec *ts) { + assert(ts); + + if (ts->tv_sec == (time_t) -1 && + ts->tv_nsec == (long) -1) + return (usec_t) -1; + + if ((usec_t) ts->tv_sec > (UINT64_MAX - (ts->tv_nsec / NSEC_PER_USEC)) / USEC_PER_SEC) + return (usec_t) -1; + + return + (usec_t) ts->tv_sec * USEC_PER_SEC + + (usec_t) ts->tv_nsec / NSEC_PER_USEC; +} + +struct timespec *timespec_store(struct timespec *ts, usec_t u) { + assert(ts); + + if (u == (usec_t) -1) { + ts->tv_sec = (time_t) -1; + ts->tv_nsec = (long) -1; + return ts; + } + + ts->tv_sec = (time_t) (u / USEC_PER_SEC); + ts->tv_nsec = (long int) ((u % USEC_PER_SEC) * NSEC_PER_USEC); + + return ts; +} + +usec_t timeval_load(const struct timeval *tv) { + assert(tv); + + if (tv->tv_sec == (time_t) -1 && + tv->tv_usec == (suseconds_t) -1) + return (usec_t) -1; + + if ((usec_t) tv->tv_sec > (UINT64_MAX - tv->tv_usec) / USEC_PER_SEC) + return (usec_t) -1; + + return + (usec_t) tv->tv_sec * USEC_PER_SEC + + (usec_t) tv->tv_usec; +} + +struct timeval *timeval_store(struct timeval *tv, usec_t u) { + assert(tv); + + if (u == (usec_t) -1) { + tv->tv_sec = (time_t) -1; + tv->tv_usec = (suseconds_t) -1; + return tv; + } + + tv->tv_sec = (time_t) (u / USEC_PER_SEC); + tv->tv_usec = (suseconds_t) (u % USEC_PER_SEC); + + return tv; +} + +char *format_timestamp(char *buf, size_t l, usec_t t) { + struct tm tm; + time_t sec; + + assert(buf); + assert(l > 0); + + if (t <= 0) + return NULL; + + sec = (time_t) (t / USEC_PER_SEC); + + if (strftime(buf, l, "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&sec, &tm)) <= 0) + return NULL; + + return buf; +} + +char *format_timestamp_relative(char *buf, size_t l, usec_t t) { + usec_t n, d; + + n = now(CLOCK_REALTIME); + + if (t <= 0 || t > n || t + USEC_PER_DAY*7 <= t) + return NULL; + + d = n - t; + + if (d >= USEC_PER_YEAR) + snprintf(buf, l, "%llu years %llu months ago", + (unsigned long long) (d / USEC_PER_YEAR), + (unsigned long long) ((d % USEC_PER_YEAR) / USEC_PER_MONTH)); + else if (d >= USEC_PER_MONTH) + snprintf(buf, l, "%llu months %llu days ago", + (unsigned long long) (d / USEC_PER_MONTH), + (unsigned long long) ((d % USEC_PER_MONTH) / USEC_PER_DAY)); + else if (d >= USEC_PER_WEEK) + snprintf(buf, l, "%llu weeks %llu days ago", + (unsigned long long) (d / USEC_PER_WEEK), + (unsigned long long) ((d % USEC_PER_WEEK) / USEC_PER_DAY)); + else if (d >= 2*USEC_PER_DAY) + snprintf(buf, l, "%llu days ago", (unsigned long long) (d / USEC_PER_DAY)); + else if (d >= 25*USEC_PER_HOUR) + snprintf(buf, l, "1 day %lluh ago", + (unsigned long long) ((d - USEC_PER_DAY) / USEC_PER_HOUR)); + else if (d >= 6*USEC_PER_HOUR) + snprintf(buf, l, "%lluh ago", + (unsigned long long) (d / USEC_PER_HOUR)); + else if (d >= USEC_PER_HOUR) + snprintf(buf, l, "%lluh %llumin ago", + (unsigned long long) (d / USEC_PER_HOUR), + (unsigned long long) ((d % USEC_PER_HOUR) / USEC_PER_MINUTE)); + else if (d >= 5*USEC_PER_MINUTE) + snprintf(buf, l, "%llumin ago", + (unsigned long long) (d / USEC_PER_MINUTE)); + else if (d >= USEC_PER_MINUTE) + snprintf(buf, l, "%llumin %llus ago", + (unsigned long long) (d / USEC_PER_MINUTE), + (unsigned long long) ((d % USEC_PER_MINUTE) / USEC_PER_SEC)); + else if (d >= USEC_PER_SEC) + snprintf(buf, l, "%llus ago", + (unsigned long long) (d / USEC_PER_SEC)); + else if (d >= USEC_PER_MSEC) + snprintf(buf, l, "%llums ago", + (unsigned long long) (d / USEC_PER_MSEC)); + else if (d > 0) + snprintf(buf, l, "%lluus ago", + (unsigned long long) d); + else + snprintf(buf, l, "now"); + + buf[l-1] = 0; + return buf; +} + +char *format_timespan(char *buf, size_t l, usec_t t, usec_t accuracy) { + static const struct { + const char *suffix; + usec_t usec; + } table[] = { + { "y", USEC_PER_YEAR }, + { "month", USEC_PER_MONTH }, + { "w", USEC_PER_WEEK }, + { "d", USEC_PER_DAY }, + { "h", USEC_PER_HOUR }, + { "min", USEC_PER_MINUTE }, + { "s", USEC_PER_SEC }, + { "ms", USEC_PER_MSEC }, + { "us", 1 }, + }; + + unsigned i; + char *p = buf; + bool something = false; + + assert(buf); + assert(l > 0); + + if (t == (usec_t) -1) + return NULL; + + if (t <= 0) { + snprintf(p, l, "0"); + p[l-1] = 0; + return p; + } + + /* The result of this function can be parsed with parse_sec */ + + for (i = 0; i < ELEMENTSOF(table); i++) { + int k; + size_t n; + bool done = false; + usec_t a, b; + + if (t <= 0) + break; + + if (t < accuracy && something) + break; + + if (t < table[i].usec) + continue; + + if (l <= 1) + break; + + a = t / table[i].usec; + b = t % table[i].usec; + + /* Let's see if we should shows this in dot notation */ + if (t < USEC_PER_MINUTE && b > 0) { + usec_t cc; + int j; + + j = 0; + for (cc = table[i].usec; cc > 1; cc /= 10) + j++; + + for (cc = accuracy; cc > 1; cc /= 10) { + b /= 10; + j--; + } + + if (j > 0) { + k = snprintf(p, l, + "%s%llu.%0*llu%s", + p > buf ? " " : "", + (unsigned long long) a, + j, + (unsigned long long) b, + table[i].suffix); + + t = 0; + done = true; + } + } + + /* No? Then let's show it normally */ + if (!done) { + k = snprintf(p, l, + "%s%llu%s", + p > buf ? " " : "", + (unsigned long long) a, + table[i].suffix); + + t = b; + } + + n = MIN((size_t) k, l); + + l -= n; + p += n; + + something = true; + } + + *p = 0; + + return buf; +} + +void dual_timestamp_serialize(FILE *f, const char *name, dual_timestamp *t) { + + assert(f); + assert(name); + assert(t); + + if (!dual_timestamp_is_set(t)) + return; + + fprintf(f, "%s=%llu %llu\n", + name, + (unsigned long long) t->realtime, + (unsigned long long) t->monotonic); +} + +void dual_timestamp_deserialize(const char *value, dual_timestamp *t) { + unsigned long long a, b; + + assert(value); + assert(t); + + if (sscanf(value, "%lli %llu", &a, &b) != 2) + log_debug("Failed to parse finish timestamp value %s", value); + else { + t->realtime = a; + t->monotonic = b; + } +} + +int parse_timestamp(const char *t, usec_t *usec) { + static const struct { + const char *name; + const int nr; + } day_nr[] = { + { "Sunday", 0 }, + { "Sun", 0 }, + { "Monday", 1 }, + { "Mon", 1 }, + { "Tuesday", 2 }, + { "Tue", 2 }, + { "Wednesday", 3 }, + { "Wed", 3 }, + { "Thursday", 4 }, + { "Thu", 4 }, + { "Friday", 5 }, + { "Fri", 5 }, + { "Saturday", 6 }, + { "Sat", 6 }, + }; + + const char *k; + struct tm tm, copy; + time_t x; + usec_t plus = 0, minus = 0, ret; + int r, weekday = -1; + unsigned i; + + /* + * Allowed syntaxes: + * + * 2012-09-22 16:34:22 + * 2012-09-22 16:34 (seconds will be set to 0) + * 2012-09-22 (time will be set to 00:00:00) + * 16:34:22 (date will be set to today) + * 16:34 (date will be set to today, seconds to 0) + * now + * yesterday (time is set to 00:00:00) + * today (time is set to 00:00:00) + * tomorrow (time is set to 00:00:00) + * +5min + * -5days + * + */ + + assert(t); + assert(usec); + + x = time(NULL); + assert_se(localtime_r(&x, &tm)); + tm.tm_isdst = -1; + + if (streq(t, "now")) + goto finish; + + else if (streq(t, "today")) { + tm.tm_sec = tm.tm_min = tm.tm_hour = 0; + goto finish; + + } else if (streq(t, "yesterday")) { + tm.tm_mday --; + tm.tm_sec = tm.tm_min = tm.tm_hour = 0; + goto finish; + + } else if (streq(t, "tomorrow")) { + tm.tm_mday ++; + tm.tm_sec = tm.tm_min = tm.tm_hour = 0; + goto finish; + + } else if (t[0] == '+') { + + r = parse_sec(t+1, &plus); + if (r < 0) + return r; + + goto finish; + } else if (t[0] == '-') { + + r = parse_sec(t+1, &minus); + if (r < 0) + return r; + + goto finish; + + } else if (endswith(t, " ago")) { + _cleanup_free_ char *z; + + z = strndup(t, strlen(t) - 4); + if (!z) + return -ENOMEM; + + r = parse_sec(z, &minus); + if (r < 0) + return r; + + goto finish; + } + + for (i = 0; i < ELEMENTSOF(day_nr); i++) { + size_t skip; + + if (!startswith_no_case(t, day_nr[i].name)) + continue; + + skip = strlen(day_nr[i].name); + if (t[skip] != ' ') + continue; + + weekday = day_nr[i].nr; + t += skip + 1; + break; + } + + copy = tm; + k = strptime(t, "%y-%m-%d %H:%M:%S", &tm); + if (k && *k == 0) + goto finish; + + tm = copy; + k = strptime(t, "%Y-%m-%d %H:%M:%S", &tm); + if (k && *k == 0) + goto finish; + + tm = copy; + k = strptime(t, "%y-%m-%d %H:%M", &tm); + if (k && *k == 0) { + tm.tm_sec = 0; + goto finish; + } + + tm = copy; + k = strptime(t, "%Y-%m-%d %H:%M", &tm); + if (k && *k == 0) { + tm.tm_sec = 0; + goto finish; + } + + tm = copy; + k = strptime(t, "%y-%m-%d", &tm); + if (k && *k == 0) { + tm.tm_sec = tm.tm_min = tm.tm_hour = 0; + goto finish; + } + + tm = copy; + k = strptime(t, "%Y-%m-%d", &tm); + if (k && *k == 0) { + tm.tm_sec = tm.tm_min = tm.tm_hour = 0; + goto finish; + } + + tm = copy; + k = strptime(t, "%H:%M:%S", &tm); + if (k && *k == 0) + goto finish; + + tm = copy; + k = strptime(t, "%H:%M", &tm); + if (k && *k == 0) { + tm.tm_sec = 0; + goto finish; + } + + return -EINVAL; + +finish: + x = mktime(&tm); + if (x == (time_t) -1) + return -EINVAL; + + if (weekday >= 0 && tm.tm_wday != weekday) + return -EINVAL; + + ret = (usec_t) x * USEC_PER_SEC; + + ret += plus; + if (ret > minus) + ret -= minus; + else + ret = 0; + + *usec = ret; + + return 0; +} + +int parse_sec(const char *t, usec_t *usec) { + static const struct { + const char *suffix; + usec_t usec; + } table[] = { + { "seconds", USEC_PER_SEC }, + { "second", USEC_PER_SEC }, + { "sec", USEC_PER_SEC }, + { "s", USEC_PER_SEC }, + { "minutes", USEC_PER_MINUTE }, + { "minute", USEC_PER_MINUTE }, + { "min", USEC_PER_MINUTE }, + { "months", USEC_PER_MONTH }, + { "month", USEC_PER_MONTH }, + { "msec", USEC_PER_MSEC }, + { "ms", USEC_PER_MSEC }, + { "m", USEC_PER_MINUTE }, + { "hours", USEC_PER_HOUR }, + { "hour", USEC_PER_HOUR }, + { "hr", USEC_PER_HOUR }, + { "h", USEC_PER_HOUR }, + { "days", USEC_PER_DAY }, + { "day", USEC_PER_DAY }, + { "d", USEC_PER_DAY }, + { "weeks", USEC_PER_WEEK }, + { "week", USEC_PER_WEEK }, + { "w", USEC_PER_WEEK }, + { "years", USEC_PER_YEAR }, + { "year", USEC_PER_YEAR }, + { "y", USEC_PER_YEAR }, + { "usec", 1ULL }, + { "us", 1ULL }, + { "", USEC_PER_SEC }, /* default is sec */ + }; + + const char *p; + usec_t r = 0; + bool something = false; + + assert(t); + assert(usec); + + p = t; + for (;;) { + long long l, z = 0; + char *e; + unsigned i, n = 0; + + p += strspn(p, WHITESPACE); + + if (*p == 0) { + if (!something) + return -EINVAL; + + break; + } + + errno = 0; + l = strtoll(p, &e, 10); + + if (errno > 0) + return -errno; + + if (l < 0) + return -ERANGE; + + if (*e == '.') { + char *b = e + 1; + + errno = 0; + z = strtoll(b, &e, 10); + if (errno > 0) + return -errno; + + if (z < 0) + return -ERANGE; + + if (e == b) + return -EINVAL; + + n = e - b; + + } else if (e == p) + return -EINVAL; + + e += strspn(e, WHITESPACE); + + for (i = 0; i < ELEMENTSOF(table); i++) + if (startswith(e, table[i].suffix)) { + usec_t k = (usec_t) z * table[i].usec; + + for (; n > 0; n--) + k /= 10; + + r += (usec_t) l * table[i].usec + k; + p = e + strlen(table[i].suffix); + + something = true; + break; + } + + if (i >= ELEMENTSOF(table)) + return -EINVAL; + + } + + *usec = r; + + return 0; +} + +int parse_nsec(const char *t, nsec_t *nsec) { + static const struct { + const char *suffix; + nsec_t nsec; + } table[] = { + { "seconds", NSEC_PER_SEC }, + { "second", NSEC_PER_SEC }, + { "sec", NSEC_PER_SEC }, + { "s", NSEC_PER_SEC }, + { "minutes", NSEC_PER_MINUTE }, + { "minute", NSEC_PER_MINUTE }, + { "min", NSEC_PER_MINUTE }, + { "months", NSEC_PER_MONTH }, + { "month", NSEC_PER_MONTH }, + { "msec", NSEC_PER_MSEC }, + { "ms", NSEC_PER_MSEC }, + { "m", NSEC_PER_MINUTE }, + { "hours", NSEC_PER_HOUR }, + { "hour", NSEC_PER_HOUR }, + { "hr", NSEC_PER_HOUR }, + { "h", NSEC_PER_HOUR }, + { "days", NSEC_PER_DAY }, + { "day", NSEC_PER_DAY }, + { "d", NSEC_PER_DAY }, + { "weeks", NSEC_PER_WEEK }, + { "week", NSEC_PER_WEEK }, + { "w", NSEC_PER_WEEK }, + { "years", NSEC_PER_YEAR }, + { "year", NSEC_PER_YEAR }, + { "y", NSEC_PER_YEAR }, + { "usec", NSEC_PER_USEC }, + { "us", NSEC_PER_USEC }, + { "nsec", 1ULL }, + { "ns", 1ULL }, + { "", 1ULL }, /* default is nsec */ + }; + + const char *p; + nsec_t r = 0; + bool something = false; + + assert(t); + assert(nsec); + + p = t; + for (;;) { + long long l, z = 0; + char *e; + unsigned i, n = 0; + + p += strspn(p, WHITESPACE); + + if (*p == 0) { + if (!something) + return -EINVAL; + + break; + } + + errno = 0; + l = strtoll(p, &e, 10); + + if (errno > 0) + return -errno; + + if (l < 0) + return -ERANGE; + + if (*e == '.') { + char *b = e + 1; + + errno = 0; + z = strtoll(b, &e, 10); + if (errno > 0) + return -errno; + + if (z < 0) + return -ERANGE; + + if (e == b) + return -EINVAL; + + n = e - b; + + } else if (e == p) + return -EINVAL; + + e += strspn(e, WHITESPACE); + + for (i = 0; i < ELEMENTSOF(table); i++) + if (startswith(e, table[i].suffix)) { + nsec_t k = (nsec_t) z * table[i].nsec; + + for (; n > 0; n--) + k /= 10; + + r += (nsec_t) l * table[i].nsec + k; + p = e + strlen(table[i].suffix); + + something = true; + break; + } + + if (i >= ELEMENTSOF(table)) + return -EINVAL; + + } + + *nsec = r; + + return 0; +} diff --git a/src/shared/time-util.h b/src/shared/time-util.h new file mode 100644 index 0000000000..f27a006891 --- /dev/null +++ b/src/shared/time-util.h @@ -0,0 +1,85 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <stdio.h> +#include <inttypes.h> + +typedef uint64_t usec_t; +typedef uint64_t nsec_t; + +#include "macro.h" + +typedef struct dual_timestamp { + usec_t realtime; + usec_t monotonic; +} dual_timestamp; + +#define MSEC_PER_SEC 1000ULL +#define USEC_PER_SEC 1000000ULL +#define USEC_PER_MSEC 1000ULL +#define NSEC_PER_SEC 1000000000ULL +#define NSEC_PER_MSEC 1000000ULL +#define NSEC_PER_USEC 1000ULL + +#define USEC_PER_MINUTE (60ULL*USEC_PER_SEC) +#define NSEC_PER_MINUTE (60ULL*NSEC_PER_SEC) +#define USEC_PER_HOUR (60ULL*USEC_PER_MINUTE) +#define NSEC_PER_HOUR (60ULL*NSEC_PER_MINUTE) +#define USEC_PER_DAY (24ULL*USEC_PER_HOUR) +#define NSEC_PER_DAY (24ULL*NSEC_PER_HOUR) +#define USEC_PER_WEEK (7ULL*USEC_PER_DAY) +#define NSEC_PER_WEEK (7ULL*NSEC_PER_DAY) +#define USEC_PER_MONTH (2629800ULL*USEC_PER_SEC) +#define NSEC_PER_MONTH (2629800ULL*NSEC_PER_SEC) +#define USEC_PER_YEAR (31557600ULL*USEC_PER_SEC) +#define NSEC_PER_YEAR (31557600ULL*NSEC_PER_SEC) + +#define FORMAT_TIMESTAMP_MAX ((4*4+1)+11+9+4+1) /* weekdays can be unicode */ +#define FORMAT_TIMESTAMP_RELATIVE_MAX 256 +#define FORMAT_TIMESPAN_MAX 64 + +usec_t now(clockid_t clock); + +dual_timestamp* dual_timestamp_get(dual_timestamp *ts); +dual_timestamp* dual_timestamp_from_realtime(dual_timestamp *ts, usec_t u); +dual_timestamp* dual_timestamp_from_monotonic(dual_timestamp *ts, usec_t u); + +#define dual_timestamp_is_set(ts) ((ts)->realtime > 0) + +usec_t timespec_load(const struct timespec *ts) _pure_; +struct timespec *timespec_store(struct timespec *ts, usec_t u); + +usec_t timeval_load(const struct timeval *tv) _pure_; +struct timeval *timeval_store(struct timeval *tv, usec_t u); + +char *format_timestamp(char *buf, size_t l, usec_t t); +char *format_timestamp_relative(char *buf, size_t l, usec_t t); +char *format_timespan(char *buf, size_t l, usec_t t, usec_t accuracy); + +void dual_timestamp_serialize(FILE *f, const char *name, dual_timestamp *t); +void dual_timestamp_deserialize(const char *value, dual_timestamp *t); + +int parse_timestamp(const char *t, usec_t *usec); + +int parse_sec(const char *t, usec_t *usec); +int parse_nsec(const char *t, nsec_t *nsec); diff --git a/src/shared/unit-name.c b/src/shared/unit-name.c new file mode 100644 index 0000000000..a809713595 --- /dev/null +++ b/src/shared/unit-name.c @@ -0,0 +1,549 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <errno.h> +#include <string.h> +#include <assert.h> + +#include "path-util.h" +#include "util.h" +#include "unit-name.h" + +#define VALID_CHARS \ + "0123456789" \ + "abcdefghijklmnopqrstuvwxyz" \ + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \ + ":-_.\\" + +static const char* const unit_type_table[_UNIT_TYPE_MAX] = { + [UNIT_SERVICE] = "service", + [UNIT_SOCKET] = "socket", + [UNIT_TARGET] = "target", + [UNIT_DEVICE] = "device", + [UNIT_MOUNT] = "mount", + [UNIT_AUTOMOUNT] = "automount", + [UNIT_SNAPSHOT] = "snapshot", + [UNIT_TIMER] = "timer", + [UNIT_SWAP] = "swap", + [UNIT_PATH] = "path", +}; + +DEFINE_STRING_TABLE_LOOKUP(unit_type, UnitType); + +static const char* const unit_load_state_table[_UNIT_LOAD_STATE_MAX] = { + [UNIT_STUB] = "stub", + [UNIT_LOADED] = "loaded", + [UNIT_ERROR] = "error", + [UNIT_MERGED] = "merged", + [UNIT_MASKED] = "masked" +}; + +DEFINE_STRING_TABLE_LOOKUP(unit_load_state, UnitLoadState); + +bool unit_name_is_valid(const char *n, bool template_ok) { + const char *e, *i, *at; + + /* Valid formats: + * + * string@instance.suffix + * string.suffix + */ + + assert(n); + + if (strlen(n) >= UNIT_NAME_MAX) + return false; + + e = strrchr(n, '.'); + if (!e || e == n) + return false; + + if (unit_type_from_string(e + 1) < 0) + return false; + + for (i = n, at = NULL; i < e; i++) { + + if (*i == '@' && !at) + at = i; + + if (!strchr("@" VALID_CHARS, *i)) + return false; + } + + if (at) { + if (at == n) + return false; + + if (!template_ok && at+1 == e) + return false; + } + + return true; +} + +bool unit_instance_is_valid(const char *i) { + assert(i); + + /* The max length depends on the length of the string, so we + * don't really check this here. */ + + if (i[0] == 0) + return false; + + /* We allow additional @ in the instance string, we do not + * allow them in the prefix! */ + + for (; *i; i++) + if (!strchr("@" VALID_CHARS, *i)) + return false; + + return true; +} + +bool unit_prefix_is_valid(const char *p) { + + /* We don't allow additional @ in the instance string */ + + if (p[0] == 0) + return false; + + for (; *p; p++) + if (!strchr(VALID_CHARS, *p)) + return false; + + return true; +} + +int unit_name_to_instance(const char *n, char **instance) { + const char *p, *d; + char *i; + + assert(n); + assert(instance); + + /* Everything past the first @ and before the last . is the instance */ + p = strchr(n, '@'); + if (!p) { + *instance = NULL; + return 0; + } + + assert_se(d = strrchr(n, '.')); + assert(p < d); + + i = strndup(p+1, d-p-1); + if (!i) + return -ENOMEM; + + *instance = i; + return 0; +} + +char *unit_name_to_prefix_and_instance(const char *n) { + const char *d; + + assert(n); + + assert_se(d = strrchr(n, '.')); + + return strndup(n, d - n); +} + +char *unit_name_to_prefix(const char *n) { + const char *p; + + p = strchr(n, '@'); + if (p) + return strndup(n, p - n); + + return unit_name_to_prefix_and_instance(n); +} + +char *unit_name_change_suffix(const char *n, const char *suffix) { + char *e, *r; + size_t a, b; + + assert(n); + assert(unit_name_is_valid(n, true)); + assert(suffix); + + assert_se(e = strrchr(n, '.')); + a = e - n; + b = strlen(suffix); + + r = new(char, a + b + 1); + if (!r) + return NULL; + + memcpy(r, n, a); + memcpy(r+a, suffix, b+1); + + return r; +} + +char *unit_name_build(const char *prefix, const char *instance, const char *suffix) { + assert(prefix); + assert(unit_prefix_is_valid(prefix)); + assert(!instance || unit_instance_is_valid(instance)); + assert(suffix); + + if (!instance) + return strappend(prefix, suffix); + + return strjoin(prefix, "@", instance, suffix, NULL); +} + +static char *do_escape_char(char c, char *t) { + *(t++) = '\\'; + *(t++) = 'x'; + *(t++) = hexchar(c >> 4); + *(t++) = hexchar(c); + return t; +} + +static char *do_escape(const char *f, char *t) { + assert(f); + assert(t); + + /* do not create units with a leading '.', like for "/.dotdir" mount points */ + if (*f == '.') { + t = do_escape_char(*f, t); + f++; + } + + for (; *f; f++) { + if (*f == '/') + *(t++) = '-'; + else if (*f == '-' || *f == '\\' || !strchr(VALID_CHARS, *f)) + t = do_escape_char(*f, t); + else + *(t++) = *f; + } + + return t; +} + +char *unit_name_escape(const char *f) { + char *r, *t; + + r = new(char, strlen(f)*4+1); + if (!r) + return NULL; + + t = do_escape(f, r); + *t = 0; + + return r; +} + +char *unit_name_unescape(const char *f) { + char *r, *t; + + assert(f); + + r = strdup(f); + if (!r) + return NULL; + + for (t = r; *f; f++) { + if (*f == '-') + *(t++) = '/'; + else if (*f == '\\') { + int a, b; + + if (f[1] != 'x' || + (a = unhexchar(f[2])) < 0 || + (b = unhexchar(f[3])) < 0) { + /* Invalid escape code, let's take it literal then */ + *(t++) = '\\'; + } else { + *(t++) = (char) ((a << 4) | b); + f += 3; + } + } else + *(t++) = *f; + } + + *t = 0; + + return r; +} + +char *unit_name_path_escape(const char *f) { + char *p, *e; + + assert(f); + + p = strdup(f); + if (!p) + return NULL; + + path_kill_slashes(p); + + if (streq(p, "/")) { + free(p); + return strdup("-"); + } + + e = unit_name_escape(p[0] == '/' ? p + 1 : p); + free(p); + + return e; +} + +char *unit_name_path_unescape(const char *f) { + char *e; + + assert(f); + + e = unit_name_unescape(f); + if (!e) + return NULL; + + if (e[0] != '/') { + char *w; + + w = strappend("/", e); + free(e); + + return w; + } + + return e; +} + +bool unit_name_is_template(const char *n) { + const char *p; + + assert(n); + + p = strchr(n, '@'); + if (!p) + return false; + + return p[1] == '.'; +} + +bool unit_name_is_instance(const char *n) { + const char *p; + + assert(n); + + p = strchr(n, '@'); + if (!p) + return false; + + return p[1] != '.'; +} + +char *unit_name_replace_instance(const char *f, const char *i) { + const char *p, *e; + char *r, *k; + size_t a, b; + + assert(f); + + p = strchr(f, '@'); + if (!p) + return strdup(f); + + e = strrchr(f, '.'); + if (!e) + assert_se(e = strchr(f, 0)); + + a = p - f; + b = strlen(i); + + r = new(char, a + 1 + b + strlen(e) + 1); + if (!r) + return NULL; + + k = mempcpy(r, f, a + 1); + k = mempcpy(k, i, b); + strcpy(k, e); + + return r; +} + +char *unit_name_template(const char *f) { + const char *p, *e; + char *r; + size_t a; + + p = strchr(f, '@'); + if (!p) + return strdup(f); + + assert_se(e = strrchr(f, '.')); + a = p - f + 1; + + r = new(char, a + strlen(e) + 1); + if (!r) + return NULL; + + strcpy(mempcpy(r, f, a), e); + return r; + +} + +char *unit_name_from_path(const char *path, const char *suffix) { + char *p, *r; + + assert(path); + assert(suffix); + + p = unit_name_path_escape(path); + if (!p) + return NULL; + + r = strappend(p, suffix); + free(p); + + return r; +} + +char *unit_name_from_path_instance(const char *prefix, const char *path, const char *suffix) { + char *p, *r; + + assert(prefix); + assert(path); + assert(suffix); + + p = unit_name_path_escape(path); + if (!p) + return NULL; + + r = strjoin(prefix, "@", p, suffix, NULL); + free(p); + + return r; +} + +char *unit_name_to_path(const char *name) { + char *w, *e; + + assert(name); + + w = unit_name_to_prefix(name); + if (!w) + return NULL; + + e = unit_name_path_unescape(w); + free(w); + + return e; +} + +char *unit_dbus_path_from_name(const char *name) { + char *e, *p; + + assert(name); + + e = bus_path_escape(name); + if (!e) + return NULL; + + p = strappend("/org/freedesktop/systemd1/unit/", e); + free(e); + + return p; +} + +char *unit_name_mangle(const char *name) { + char *r, *t; + const char *f; + + assert(name); + + /* Try to turn a string that might not be a unit name into a + * sensible unit name. */ + + if (is_device_path(name)) + return unit_name_from_path(name, ".device"); + + if (path_is_absolute(name)) + return unit_name_from_path(name, ".mount"); + + /* We'll only escape the obvious characters here, to play + * safe. */ + + r = new(char, strlen(name) * 4 + 1 + sizeof(".service")-1); + if (!r) + return NULL; + + for (f = name, t = r; *f; f++) { + if (*f == '/') + *(t++) = '-'; + else if (!strchr("@" VALID_CHARS, *f)) + t = do_escape_char(*f, t); + else + *(t++) = *f; + } + + if (unit_name_to_type(name) < 0) + strcpy(t, ".service"); + else + *t = 0; + + return r; +} + +char *snapshot_name_mangle(const char *name) { + char *r, *t; + const char *f; + + assert(name); + + /* Similar to unit_name_mangle(), but is called when we know + * that this is about snapshot units. */ + + r = new(char, strlen(name) * 4 + 1 + sizeof(".snapshot")-1); + if (!r) + return NULL; + + for (f = name, t = r; *f; f++) { + if (*f == '/') + *(t++) = '-'; + else if (!strchr(VALID_CHARS, *f)) + t = do_escape_char(*f, t); + else + *(t++) = *f; + } + + if (!endswith(name, ".snapshot")) + strcpy(t, ".snapshot"); + else + *t = 0; + + return r; +} + +UnitType unit_name_to_type(const char *n) { + const char *e; + + assert(n); + + e = strrchr(n, '.'); + if (!e) + return _UNIT_TYPE_INVALID; + + return unit_type_from_string(e + 1); +} diff --git a/src/shared/unit-name.h b/src/shared/unit-name.h new file mode 100644 index 0000000000..9eca8eb3c1 --- /dev/null +++ b/src/shared/unit-name.h @@ -0,0 +1,97 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <stdbool.h> + +#include "macro.h" + +#define UNIT_NAME_MAX 256 + +typedef enum UnitType UnitType; +typedef enum UnitLoadState UnitLoadState; + +enum UnitType { + UNIT_SERVICE = 0, + UNIT_SOCKET, + UNIT_TARGET, + UNIT_DEVICE, + UNIT_MOUNT, + UNIT_AUTOMOUNT, + UNIT_SNAPSHOT, + UNIT_TIMER, + UNIT_SWAP, + UNIT_PATH, + _UNIT_TYPE_MAX, + _UNIT_TYPE_INVALID = -1 +}; + +enum UnitLoadState { + UNIT_STUB = 0, + UNIT_LOADED, + UNIT_ERROR, + UNIT_MERGED, + UNIT_MASKED, + _UNIT_LOAD_STATE_MAX, + _UNIT_LOAD_STATE_INVALID = -1 +}; + +const char *unit_type_to_string(UnitType i) _const_; +UnitType unit_type_from_string(const char *s) _pure_; + +const char *unit_load_state_to_string(UnitLoadState i) _const_; +UnitLoadState unit_load_state_from_string(const char *s) _pure_; + +int unit_name_to_instance(const char *n, char **instance); +char* unit_name_to_prefix(const char *n); +char* unit_name_to_prefix_and_instance(const char *n); + +bool unit_name_is_valid(const char *n, bool template_ok) _pure_; +bool unit_prefix_is_valid(const char *p) _pure_; +bool unit_instance_is_valid(const char *i) _pure_; + +UnitType unit_name_to_type(const char *n) _pure_; + +char *unit_name_change_suffix(const char *n, const char *suffix); + +char *unit_name_build(const char *prefix, const char *instance, const char *suffix); + +char *unit_name_escape(const char *f); +char *unit_name_unescape(const char *f); +char *unit_name_path_escape(const char *f); +char *unit_name_path_unescape(const char *f); + +bool unit_name_is_template(const char *n) _pure_; +bool unit_name_is_instance(const char *n) _pure_; + +char *unit_name_replace_instance(const char *f, const char *i); + +char *unit_name_template(const char *f); + +char *unit_name_from_path(const char *path, const char *suffix); +char *unit_name_from_path_instance(const char *prefix, const char *path, const char *suffix); +char *unit_name_to_path(const char *name); + +char *unit_dbus_path_from_name(const char *name); + +char *unit_name_mangle(const char *name); +char *snapshot_name_mangle(const char *name); diff --git a/src/shared/utf8.c b/src/shared/utf8.c new file mode 100644 index 0000000000..3964e8b1ce --- /dev/null +++ b/src/shared/utf8.c @@ -0,0 +1,322 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2012 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +/* This file is based on the GLIB utf8 validation functions. The + * original license text follows. */ + +/* gutf8.c - Operations on UTF-8 strings. + * + * Copyright (C) 1999 Tom Tromey + * Copyright (C) 2000 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <errno.h> +#include <stdlib.h> +#include <inttypes.h> +#include <string.h> +#include <stdbool.h> + +#include "utf8.h" +#include "util.h" + +#define FILTER_CHAR '_' + +static inline bool is_unicode_valid(uint32_t ch) { + + if (ch >= 0x110000) /* End of unicode space */ + return false; + if ((ch & 0xFFFFF800) == 0xD800) /* Reserved area for UTF-16 */ + return false; + if ((ch >= 0xFDD0) && (ch <= 0xFDEF)) /* Reserved */ + return false; + if ((ch & 0xFFFE) == 0xFFFE) /* BOM (Byte Order Mark) */ + return false; + + return true; +} + +static inline bool is_continuation_char(uint8_t ch) { + if ((ch & 0xc0) != 0x80) /* 10xxxxxx */ + return false; + return true; +} + +static inline void merge_continuation_char(uint32_t *u_ch, uint8_t ch) { + *u_ch <<= 6; + *u_ch |= ch & 0x3f; +} + +static bool is_unicode_control(uint32_t ch) { + + /* + 0 to ' '-1 is the C0 range. + DEL=0x7F, and DEL+1 to 0x9F is C1 range. + '\t' is in C0 range, but more or less harmless and commonly used. + */ + + return (ch < ' ' && ch != '\t') || + (0x7F <= ch && ch <= 0x9F); +} + +char* utf8_is_printable_n(const char* str, size_t length) { + uint32_t val = 0; + uint32_t min = 0; + const uint8_t *p; + + assert(str); + + for (p = (const uint8_t*) str; length; p++, length--) { + if (*p < 128) { + val = *p; + } else { + if ((*p & 0xe0) == 0xc0) { /* 110xxxxx two-char seq. */ + min = 128; + val = (uint32_t) (*p & 0x1e); + goto ONE_REMAINING; + } else if ((*p & 0xf0) == 0xe0) { /* 1110xxxx three-char seq.*/ + min = (1 << 11); + val = (uint32_t) (*p & 0x0f); + goto TWO_REMAINING; + } else if ((*p & 0xf8) == 0xf0) { /* 11110xxx four-char seq */ + min = (1 << 16); + val = (uint32_t) (*p & 0x07); + } else + goto error; + + p++; + length--; + if (!length || !is_continuation_char(*p)) + goto error; + merge_continuation_char(&val, *p); + + TWO_REMAINING: + p++; + length--; + if (!is_continuation_char(*p)) + goto error; + merge_continuation_char(&val, *p); + + ONE_REMAINING: + p++; + length--; + if (!is_continuation_char(*p)) + goto error; + merge_continuation_char(&val, *p); + + if (val < min) + goto error; + } + + if (is_unicode_control(val)) + goto error; + } + + return (char*) str; + +error: + return NULL; +} + +static char* utf8_validate(const char *str, char *output) { + uint32_t val = 0; + uint32_t min = 0; + const uint8_t *p, *last; + int size; + uint8_t *o; + + assert(str); + + o = (uint8_t*) output; + for (p = (const uint8_t*) str; *p; p++) { + if (*p < 128) { + if (o) + *o = *p; + } else { + last = p; + + if ((*p & 0xe0) == 0xc0) { /* 110xxxxx two-char seq. */ + size = 2; + min = 128; + val = (uint32_t) (*p & 0x1e); + goto ONE_REMAINING; + } else if ((*p & 0xf0) == 0xe0) { /* 1110xxxx three-char seq.*/ + size = 3; + min = (1 << 11); + val = (uint32_t) (*p & 0x0f); + goto TWO_REMAINING; + } else if ((*p & 0xf8) == 0xf0) { /* 11110xxx four-char seq */ + size = 4; + min = (1 << 16); + val = (uint32_t) (*p & 0x07); + } else + goto error; + + p++; + if (!is_continuation_char(*p)) + goto error; + merge_continuation_char(&val, *p); + + TWO_REMAINING: + p++; + if (!is_continuation_char(*p)) + goto error; + merge_continuation_char(&val, *p); + + ONE_REMAINING: + p++; + if (!is_continuation_char(*p)) + goto error; + merge_continuation_char(&val, *p); + + if (val < min) + goto error; + + if (!is_unicode_valid(val)) + goto error; + + if (o) { + memcpy(o, last, (size_t) size); + o += size; + } + + continue; + + error: + if (o) { + *o = FILTER_CHAR; + p = last; /* We retry at the next character */ + } else + goto failure; + } + + if (o) + o++; + } + + if (o) { + *o = '\0'; + return output; + } + + return (char*) str; + +failure: + return NULL; +} + +char* utf8_is_valid (const char *str) { + return utf8_validate(str, NULL); +} + +char* utf8_filter (const char *str) { + char *new_str; + + assert(str); + + new_str = malloc(strlen(str) + 1); + if (!new_str) + return NULL; + + return utf8_validate(str, new_str); +} + +char *ascii_is_valid(const char *str) { + const char *p; + + assert(str); + + for (p = str; *p; p++) + if ((unsigned char) *p >= 128) + return NULL; + + return (char*) str; +} + +char *ascii_filter(const char *str) { + const char *s; + char *r, *d; + size_t l; + + assert(str); + + l = strlen(str); + r = malloc(l + 1); + if (!r) + return NULL; + + for (s = str, d = r; *s; s++) + if ((unsigned char) *s < 128) + *(d++) = *s; + + *d = 0; + + return r; +} + +char *utf16_to_utf8(const void *s, size_t length) { + char *r; + const uint8_t *f; + uint8_t *t; + + r = new(char, (length*3+1)/2 + 1); + if (!r) + return NULL; + + t = (uint8_t*) r; + + for (f = s; f < (const uint8_t*) s + length; f += 2) { + uint16_t c; + + c = (f[1] << 8) | f[0]; + + if (c == 0) { + *t = 0; + return r; + } else if (c < 0x80) { + *(t++) = (uint8_t) c; + } else if (c < 0x800) { + *(t++) = (uint8_t) (0xc0 | (c >> 6)); + *(t++) = (uint8_t) (0x80 | (c & 0x3f)); + } else { + *(t++) = (uint8_t) (0xe0 | (c >> 12)); + *(t++) = (uint8_t) (0x80 | ((c >> 6) & 0x3f)); + *(t++) = (uint8_t) (0x80 | (c & 0x3f)); + } + } + + *t = 0; + + return r; +} diff --git a/src/shared/utf8.h b/src/shared/utf8.h new file mode 100644 index 0000000000..794ae15ab9 --- /dev/null +++ b/src/shared/utf8.h @@ -0,0 +1,34 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2012 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "macro.h" + +char *utf8_is_valid(const char *s) _pure_; +char *ascii_is_valid(const char *s) _pure_; + +char *utf8_is_printable_n(const char* str, size_t length) _pure_; + +char *utf8_filter(const char *s); +char *ascii_filter(const char *s); + +char *utf16_to_utf8(const void *s, size_t length); diff --git a/src/shared/util.c b/src/shared/util.c new file mode 100644 index 0000000000..673e0da6b6 --- /dev/null +++ b/src/shared/util.c @@ -0,0 +1,5849 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <assert.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <stdlib.h> +#include <signal.h> +#include <stdio.h> +#include <syslog.h> +#include <sched.h> +#include <sys/resource.h> +#include <linux/sched.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <dirent.h> +#include <sys/ioctl.h> +#include <linux/vt.h> +#include <linux/tiocl.h> +#include <termios.h> +#include <stdarg.h> +#include <sys/inotify.h> +#include <sys/poll.h> +#include <libgen.h> +#include <ctype.h> +#include <sys/prctl.h> +#include <sys/utsname.h> +#include <pwd.h> +#include <netinet/ip.h> +#include <linux/kd.h> +#include <dlfcn.h> +#include <sys/wait.h> +#include <sys/time.h> +#include <glob.h> +#include <grp.h> +#include <sys/mman.h> +#include <sys/vfs.h> +#include <linux/magic.h> +#include <limits.h> +#include <langinfo.h> +#include <locale.h> +#include <libgen.h> + +#include "macro.h" +#include "util.h" +#include "ioprio.h" +#include "missing.h" +#include "log.h" +#include "strv.h" +#include "label.h" +#include "path-util.h" +#include "exit-status.h" +#include "hashmap.h" +#include "env-util.h" +#include "fileio.h" + +int saved_argc = 0; +char **saved_argv = NULL; + +static volatile unsigned cached_columns = 0; +static volatile unsigned cached_lines = 0; + +size_t page_size(void) { + static __thread size_t pgsz = 0; + long r; + + if (_likely_(pgsz > 0)) + return pgsz; + + r = sysconf(_SC_PAGESIZE); + assert(r > 0); + + pgsz = (size_t) r; + return pgsz; +} + +bool streq_ptr(const char *a, const char *b) { + + /* Like streq(), but tries to make sense of NULL pointers */ + + if (a && b) + return streq(a, b); + + if (!a && !b) + return true; + + return false; +} + +char* endswith(const char *s, const char *postfix) { + size_t sl, pl; + + assert(s); + assert(postfix); + + sl = strlen(s); + pl = strlen(postfix); + + if (pl == 0) + return (char*) s + sl; + + if (sl < pl) + return NULL; + + if (memcmp(s + sl - pl, postfix, pl) != 0) + return NULL; + + return (char*) s + sl - pl; +} + +char* startswith(const char *s, const char *prefix) { + const char *a, *b; + + assert(s); + assert(prefix); + + a = s, b = prefix; + for (;;) { + if (*b == 0) + return (char*) a; + if (*a != *b) + return NULL; + + a++, b++; + } +} + +char* startswith_no_case(const char *s, const char *prefix) { + const char *a, *b; + + assert(s); + assert(prefix); + + a = s, b = prefix; + for (;;) { + if (*b == 0) + return (char*) a; + if (tolower(*a) != tolower(*b)) + return NULL; + + a++, b++; + } +} + +bool first_word(const char *s, const char *word) { + size_t sl, wl; + + assert(s); + assert(word); + + sl = strlen(s); + wl = strlen(word); + + if (sl < wl) + return false; + + if (wl == 0) + return true; + + if (memcmp(s, word, wl) != 0) + return false; + + return s[wl] == 0 || + strchr(WHITESPACE, s[wl]); +} + +int close_nointr(int fd) { + int r; + + assert(fd >= 0); + r = close(fd); + + /* Just ignore EINTR; a retry loop is the wrong + * thing to do on Linux. + * + * http://lkml.indiana.edu/hypermail/linux/kernel/0509.1/0877.html + * https://bugzilla.gnome.org/show_bug.cgi?id=682819 + * http://utcc.utoronto.ca/~cks/space/blog/unix/CloseEINTR + * https://sites.google.com/site/michaelsafyan/software-engineering/checkforeintrwheninvokingclosethinkagain + */ + if (_unlikely_(r < 0 && errno == EINTR)) + return 0; + else if (r >= 0) + return r; + else + return -errno; +} + +void close_nointr_nofail(int fd) { + PROTECT_ERRNO; + + /* like close_nointr() but cannot fail, and guarantees errno + * is unchanged */ + + assert_se(close_nointr(fd) == 0); +} + +void close_many(const int fds[], unsigned n_fd) { + unsigned i; + + assert(fds || n_fd <= 0); + + for (i = 0; i < n_fd; i++) + close_nointr_nofail(fds[i]); +} + +int unlink_noerrno(const char *path) { + PROTECT_ERRNO; + int r; + + r = unlink(path); + if (r < 0) + return -errno; + + return 0; +} + +int parse_boolean(const char *v) { + assert(v); + + if (streq(v, "1") || v[0] == 'y' || v[0] == 'Y' || v[0] == 't' || v[0] == 'T' || strcaseeq(v, "on")) + return 1; + else if (streq(v, "0") || v[0] == 'n' || v[0] == 'N' || v[0] == 'f' || v[0] == 'F' || strcaseeq(v, "off")) + return 0; + + return -EINVAL; +} + +int parse_pid(const char *s, pid_t* ret_pid) { + unsigned long ul = 0; + pid_t pid; + int r; + + assert(s); + assert(ret_pid); + + r = safe_atolu(s, &ul); + if (r < 0) + return r; + + pid = (pid_t) ul; + + if ((unsigned long) pid != ul) + return -ERANGE; + + if (pid <= 0) + return -ERANGE; + + *ret_pid = pid; + return 0; +} + +int parse_uid(const char *s, uid_t* ret_uid) { + unsigned long ul = 0; + uid_t uid; + int r; + + assert(s); + assert(ret_uid); + + r = safe_atolu(s, &ul); + if (r < 0) + return r; + + uid = (uid_t) ul; + + if ((unsigned long) uid != ul) + return -ERANGE; + + *ret_uid = uid; + return 0; +} + +int safe_atou(const char *s, unsigned *ret_u) { + char *x = NULL; + unsigned long l; + + assert(s); + assert(ret_u); + + errno = 0; + l = strtoul(s, &x, 0); + + if (!x || x == s || *x || errno) + return errno > 0 ? -errno : -EINVAL; + + if ((unsigned long) (unsigned) l != l) + return -ERANGE; + + *ret_u = (unsigned) l; + return 0; +} + +int safe_atoi(const char *s, int *ret_i) { + char *x = NULL; + long l; + + assert(s); + assert(ret_i); + + errno = 0; + l = strtol(s, &x, 0); + + if (!x || x == s || *x || errno) + return errno > 0 ? -errno : -EINVAL; + + if ((long) (int) l != l) + return -ERANGE; + + *ret_i = (int) l; + return 0; +} + +int safe_atollu(const char *s, long long unsigned *ret_llu) { + char *x = NULL; + unsigned long long l; + + assert(s); + assert(ret_llu); + + errno = 0; + l = strtoull(s, &x, 0); + + if (!x || x == s || *x || errno) + return errno ? -errno : -EINVAL; + + *ret_llu = l; + return 0; +} + +int safe_atolli(const char *s, long long int *ret_lli) { + char *x = NULL; + long long l; + + assert(s); + assert(ret_lli); + + errno = 0; + l = strtoll(s, &x, 0); + + if (!x || x == s || *x || errno) + return errno ? -errno : -EINVAL; + + *ret_lli = l; + return 0; +} + +int safe_atod(const char *s, double *ret_d) { + char *x = NULL; + double d; + + assert(s); + assert(ret_d); + + RUN_WITH_LOCALE(LC_NUMERIC_MASK, "C") { + errno = 0; + d = strtod(s, &x); + } + + if (!x || x == s || *x || errno) + return errno ? -errno : -EINVAL; + + *ret_d = (double) d; + return 0; +} + +/* Split a string into words. */ +char *split(const char *c, size_t *l, const char *separator, char **state) { + char *current; + + current = *state ? *state : (char*) c; + + if (!*current || *c == 0) + return NULL; + + current += strspn(current, separator); + *l = strcspn(current, separator); + *state = current+*l; + + return (char*) current; +} + +/* Split a string into words, but consider strings enclosed in '' and + * "" as words even if they include spaces. */ +char *split_quoted(const char *c, size_t *l, char **state) { + char *current, *e; + bool escaped = false; + + current = *state ? *state : (char*) c; + + if (!*current || *c == 0) + return NULL; + + current += strspn(current, WHITESPACE); + + if (*current == '\'') { + current ++; + + for (e = current; *e; e++) { + if (escaped) + escaped = false; + else if (*e == '\\') + escaped = true; + else if (*e == '\'') + break; + } + + *l = e-current; + *state = *e == 0 ? e : e+1; + } else if (*current == '\"') { + current ++; + + for (e = current; *e; e++) { + if (escaped) + escaped = false; + else if (*e == '\\') + escaped = true; + else if (*e == '\"') + break; + } + + *l = e-current; + *state = *e == 0 ? e : e+1; + } else { + for (e = current; *e; e++) { + if (escaped) + escaped = false; + else if (*e == '\\') + escaped = true; + else if (strchr(WHITESPACE, *e)) + break; + } + *l = e-current; + *state = e; + } + + return (char*) current; +} + +int get_parent_of_pid(pid_t pid, pid_t *_ppid) { + int r; + _cleanup_fclose_ FILE *f = NULL; + char line[LINE_MAX]; + long unsigned ppid; + const char *p; + + assert(pid >= 0); + assert(_ppid); + + if (pid == 0) { + *_ppid = getppid(); + return 0; + } + + p = procfs_file_alloca(pid, "stat"); + f = fopen(p, "re"); + if (!f) + return -errno; + + if (!fgets(line, sizeof(line), f)) { + r = feof(f) ? -EIO : -errno; + return r; + } + + /* Let's skip the pid and comm fields. The latter is enclosed + * in () but does not escape any () in its value, so let's + * skip over it manually */ + + p = strrchr(line, ')'); + if (!p) + return -EIO; + + p++; + + if (sscanf(p, " " + "%*c " /* state */ + "%lu ", /* ppid */ + &ppid) != 1) + return -EIO; + + if ((long unsigned) (pid_t) ppid != ppid) + return -ERANGE; + + *_ppid = (pid_t) ppid; + + return 0; +} + +int get_starttime_of_pid(pid_t pid, unsigned long long *st) { + _cleanup_fclose_ FILE *f = NULL; + char line[LINE_MAX]; + const char *p; + + assert(pid >= 0); + assert(st); + + if (pid == 0) + p = "/proc/self/stat"; + else + p = procfs_file_alloca(pid, "stat"); + + f = fopen(p, "re"); + if (!f) + return -errno; + + if (!fgets(line, sizeof(line), f)) { + if (ferror(f)) + return -errno; + + return -EIO; + } + + /* Let's skip the pid and comm fields. The latter is enclosed + * in () but does not escape any () in its value, so let's + * skip over it manually */ + + p = strrchr(line, ')'); + if (!p) + return -EIO; + + p++; + + if (sscanf(p, " " + "%*c " /* state */ + "%*d " /* ppid */ + "%*d " /* pgrp */ + "%*d " /* session */ + "%*d " /* tty_nr */ + "%*d " /* tpgid */ + "%*u " /* flags */ + "%*u " /* minflt */ + "%*u " /* cminflt */ + "%*u " /* majflt */ + "%*u " /* cmajflt */ + "%*u " /* utime */ + "%*u " /* stime */ + "%*d " /* cutime */ + "%*d " /* cstime */ + "%*d " /* priority */ + "%*d " /* nice */ + "%*d " /* num_threads */ + "%*d " /* itrealvalue */ + "%llu " /* starttime */, + st) != 1) + return -EIO; + + return 0; +} + +int fchmod_umask(int fd, mode_t m) { + mode_t u; + int r; + + u = umask(0777); + r = fchmod(fd, m & (~u)) < 0 ? -errno : 0; + umask(u); + + return r; +} + +char *truncate_nl(char *s) { + assert(s); + + s[strcspn(s, NEWLINE)] = 0; + return s; +} + +int get_process_comm(pid_t pid, char **name) { + const char *p; + + assert(name); + assert(pid >= 0); + + if (pid == 0) + p = "/proc/self/comm"; + else + p = procfs_file_alloca(pid, "comm"); + + return read_one_line_file(p, name); +} + +int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char **line) { + _cleanup_fclose_ FILE *f = NULL; + char *r = NULL, *k; + const char *p; + int c; + + assert(line); + assert(pid >= 0); + + if (pid == 0) + p = "/proc/self/cmdline"; + else + p = procfs_file_alloca(pid, "cmdline"); + + f = fopen(p, "re"); + if (!f) + return -errno; + + if (max_length == 0) { + size_t len = 0, allocated = 0; + + while ((c = getc(f)) != EOF) { + + if (!GREEDY_REALLOC(r, allocated, len+2)) { + free(r); + return -ENOMEM; + } + + r[len++] = isprint(c) ? c : ' '; + } + + if (len > 0) + r[len-1] = 0; + + } else { + bool space = false; + size_t left; + + r = new(char, max_length); + if (!r) + return -ENOMEM; + + k = r; + left = max_length; + while ((c = getc(f)) != EOF) { + + if (isprint(c)) { + if (space) { + if (left <= 4) + break; + + *(k++) = ' '; + left--; + space = false; + } + + if (left <= 4) + break; + + *(k++) = (char) c; + left--; + } else + space = true; + } + + if (left <= 4) { + size_t n = MIN(left-1, 3U); + memcpy(k, "...", n); + k[n] = 0; + } else + *k = 0; + } + + /* Kernel threads have no argv[] */ + if (r == NULL || r[0] == 0) { + char *t; + int h; + + free(r); + + if (!comm_fallback) + return -ENOENT; + + h = get_process_comm(pid, &t); + if (h < 0) + return h; + + r = strjoin("[", t, "]", NULL); + free(t); + + if (!r) + return -ENOMEM; + } + + *line = r; + return 0; +} + +int is_kernel_thread(pid_t pid) { + const char *p; + size_t count; + char c; + bool eof; + FILE *f; + + if (pid == 0) + return 0; + + assert(pid > 0); + + p = procfs_file_alloca(pid, "cmdline"); + f = fopen(p, "re"); + if (!f) + return -errno; + + count = fread(&c, 1, 1, f); + eof = feof(f); + fclose(f); + + /* Kernel threads have an empty cmdline */ + + if (count <= 0) + return eof ? 1 : -errno; + + return 0; +} + + +int get_process_exe(pid_t pid, char **name) { + const char *p; + + assert(pid >= 0); + assert(name); + + if (pid == 0) + p = "/proc/self/exe"; + else + p = procfs_file_alloca(pid, "exe"); + + return readlink_malloc(p, name); +} + +static int get_process_id(pid_t pid, const char *field, uid_t *uid) { + _cleanup_fclose_ FILE *f = NULL; + char line[LINE_MAX]; + const char *p; + + assert(field); + assert(uid); + + if (pid == 0) + return getuid(); + + p = procfs_file_alloca(pid, "status"); + f = fopen(p, "re"); + if (!f) + return -errno; + + FOREACH_LINE(line, f, return -errno) { + char *l; + + l = strstrip(line); + + if (startswith(l, field)) { + l += strlen(field); + l += strspn(l, WHITESPACE); + + l[strcspn(l, WHITESPACE)] = 0; + + return parse_uid(l, uid); + } + } + + return -EIO; +} + +int get_process_uid(pid_t pid, uid_t *uid) { + return get_process_id(pid, "Uid:", uid); +} + +int get_process_gid(pid_t pid, gid_t *gid) { + assert_cc(sizeof(uid_t) == sizeof(gid_t)); + return get_process_id(pid, "Gid:", gid); +} + +char *strnappend(const char *s, const char *suffix, size_t b) { + size_t a; + char *r; + + if (!s && !suffix) + return strdup(""); + + if (!s) + return strndup(suffix, b); + + if (!suffix) + return strdup(s); + + assert(s); + assert(suffix); + + a = strlen(s); + if (b > ((size_t) -1) - a) + return NULL; + + r = new(char, a+b+1); + if (!r) + return NULL; + + memcpy(r, s, a); + memcpy(r+a, suffix, b); + r[a+b] = 0; + + return r; +} + +char *strappend(const char *s, const char *suffix) { + return strnappend(s, suffix, suffix ? strlen(suffix) : 0); +} + +int readlink_malloc(const char *p, char **r) { + size_t l = 100; + + assert(p); + assert(r); + + for (;;) { + char *c; + ssize_t n; + + if (!(c = new(char, l))) + return -ENOMEM; + + if ((n = readlink(p, c, l-1)) < 0) { + int ret = -errno; + free(c); + return ret; + } + + if ((size_t) n < l-1) { + c[n] = 0; + *r = c; + return 0; + } + + free(c); + l *= 2; + } +} + +int readlink_and_make_absolute(const char *p, char **r) { + char *target, *k; + int j; + + assert(p); + assert(r); + + if ((j = readlink_malloc(p, &target)) < 0) + return j; + + k = file_in_same_dir(p, target); + free(target); + + if (!k) + return -ENOMEM; + + *r = k; + return 0; +} + +int readlink_and_canonicalize(const char *p, char **r) { + char *t, *s; + int j; + + assert(p); + assert(r); + + j = readlink_and_make_absolute(p, &t); + if (j < 0) + return j; + + s = canonicalize_file_name(t); + if (s) { + free(t); + *r = s; + } else + *r = t; + + path_kill_slashes(*r); + + return 0; +} + +int reset_all_signal_handlers(void) { + int sig; + + for (sig = 1; sig < _NSIG; sig++) { + struct sigaction sa = { + .sa_handler = SIG_DFL, + .sa_flags = SA_RESTART, + }; + + if (sig == SIGKILL || sig == SIGSTOP) + continue; + + /* On Linux the first two RT signals are reserved by + * glibc, and sigaction() will return EINVAL for them. */ + if ((sigaction(sig, &sa, NULL) < 0)) + if (errno != EINVAL) + return -errno; + } + + return 0; +} + +char *strstrip(char *s) { + char *e; + + /* Drops trailing whitespace. Modifies the string in + * place. Returns pointer to first non-space character */ + + s += strspn(s, WHITESPACE); + + for (e = strchr(s, 0); e > s; e --) + if (!strchr(WHITESPACE, e[-1])) + break; + + *e = 0; + + return s; +} + +char *delete_chars(char *s, const char *bad) { + char *f, *t; + + /* Drops all whitespace, regardless where in the string */ + + for (f = s, t = s; *f; f++) { + if (strchr(bad, *f)) + continue; + + *(t++) = *f; + } + + *t = 0; + + return s; +} + +bool in_charset(const char *s, const char* charset) { + const char *i; + + assert(s); + assert(charset); + + for (i = s; *i; i++) + if (!strchr(charset, *i)) + return false; + + return true; +} + +char *file_in_same_dir(const char *path, const char *filename) { + char *e, *r; + size_t k; + + assert(path); + assert(filename); + + /* This removes the last component of path and appends + * filename, unless the latter is absolute anyway or the + * former isn't */ + + if (path_is_absolute(filename)) + return strdup(filename); + + if (!(e = strrchr(path, '/'))) + return strdup(filename); + + k = strlen(filename); + if (!(r = new(char, e-path+1+k+1))) + return NULL; + + memcpy(r, path, e-path+1); + memcpy(r+(e-path)+1, filename, k+1); + + return r; +} + +int rmdir_parents(const char *path, const char *stop) { + size_t l; + int r = 0; + + assert(path); + assert(stop); + + l = strlen(path); + + /* Skip trailing slashes */ + while (l > 0 && path[l-1] == '/') + l--; + + while (l > 0) { + char *t; + + /* Skip last component */ + while (l > 0 && path[l-1] != '/') + l--; + + /* Skip trailing slashes */ + while (l > 0 && path[l-1] == '/') + l--; + + if (l <= 0) + break; + + if (!(t = strndup(path, l))) + return -ENOMEM; + + if (path_startswith(stop, t)) { + free(t); + return 0; + } + + r = rmdir(t); + free(t); + + if (r < 0) + if (errno != ENOENT) + return -errno; + } + + return 0; +} + +char hexchar(int x) { + static const char table[16] = "0123456789abcdef"; + + return table[x & 15]; +} + +int unhexchar(char c) { + + if (c >= '0' && c <= '9') + return c - '0'; + + if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + + if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + + return -1; +} + +char *hexmem(const void *p, size_t l) { + char *r, *z; + const uint8_t *x; + + z = r = malloc(l * 2 + 1); + if (!r) + return NULL; + + for (x = p; x < (const uint8_t*) p + l; x++) { + *(z++) = hexchar(*x >> 4); + *(z++) = hexchar(*x & 15); + } + + *z = 0; + return r; +} + +void *unhexmem(const char *p, size_t l) { + uint8_t *r, *z; + const char *x; + + assert(p); + + z = r = malloc((l + 1) / 2 + 1); + if (!r) + return NULL; + + for (x = p; x < p + l; x += 2) { + int a, b; + + a = unhexchar(x[0]); + if (x+1 < p + l) + b = unhexchar(x[1]); + else + b = 0; + + *(z++) = (uint8_t) a << 4 | (uint8_t) b; + } + + *z = 0; + return r; +} + +char octchar(int x) { + return '0' + (x & 7); +} + +int unoctchar(char c) { + + if (c >= '0' && c <= '7') + return c - '0'; + + return -1; +} + +char decchar(int x) { + return '0' + (x % 10); +} + +int undecchar(char c) { + + if (c >= '0' && c <= '9') + return c - '0'; + + return -1; +} + +char *cescape(const char *s) { + char *r, *t; + const char *f; + + assert(s); + + /* Does C style string escaping. */ + + r = new(char, strlen(s)*4 + 1); + if (!r) + return NULL; + + for (f = s, t = r; *f; f++) + + switch (*f) { + + case '\a': + *(t++) = '\\'; + *(t++) = 'a'; + break; + case '\b': + *(t++) = '\\'; + *(t++) = 'b'; + break; + case '\f': + *(t++) = '\\'; + *(t++) = 'f'; + break; + case '\n': + *(t++) = '\\'; + *(t++) = 'n'; + break; + case '\r': + *(t++) = '\\'; + *(t++) = 'r'; + break; + case '\t': + *(t++) = '\\'; + *(t++) = 't'; + break; + case '\v': + *(t++) = '\\'; + *(t++) = 'v'; + break; + case '\\': + *(t++) = '\\'; + *(t++) = '\\'; + break; + case '"': + *(t++) = '\\'; + *(t++) = '"'; + break; + case '\'': + *(t++) = '\\'; + *(t++) = '\''; + break; + + default: + /* For special chars we prefer octal over + * hexadecimal encoding, simply because glib's + * g_strescape() does the same */ + if ((*f < ' ') || (*f >= 127)) { + *(t++) = '\\'; + *(t++) = octchar((unsigned char) *f >> 6); + *(t++) = octchar((unsigned char) *f >> 3); + *(t++) = octchar((unsigned char) *f); + } else + *(t++) = *f; + break; + } + + *t = 0; + + return r; +} + +char *cunescape_length_with_prefix(const char *s, size_t length, const char *prefix) { + char *r, *t; + const char *f; + size_t pl; + + assert(s); + + /* Undoes C style string escaping, and optionally prefixes it. */ + + pl = prefix ? strlen(prefix) : 0; + + r = new(char, pl+length+1); + if (!r) + return r; + + if (prefix) + memcpy(r, prefix, pl); + + for (f = s, t = r + pl; f < s + length; f++) { + + if (*f != '\\') { + *(t++) = *f; + continue; + } + + f++; + + switch (*f) { + + case 'a': + *(t++) = '\a'; + break; + case 'b': + *(t++) = '\b'; + break; + case 'f': + *(t++) = '\f'; + break; + case 'n': + *(t++) = '\n'; + break; + case 'r': + *(t++) = '\r'; + break; + case 't': + *(t++) = '\t'; + break; + case 'v': + *(t++) = '\v'; + break; + case '\\': + *(t++) = '\\'; + break; + case '"': + *(t++) = '"'; + break; + case '\'': + *(t++) = '\''; + break; + + case 's': + /* This is an extension of the XDG syntax files */ + *(t++) = ' '; + break; + + case 'x': { + /* hexadecimal encoding */ + int a, b; + + a = unhexchar(f[1]); + b = unhexchar(f[2]); + + if (a < 0 || b < 0) { + /* Invalid escape code, let's take it literal then */ + *(t++) = '\\'; + *(t++) = 'x'; + } else { + *(t++) = (char) ((a << 4) | b); + f += 2; + } + + break; + } + + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': { + /* octal encoding */ + int a, b, c; + + a = unoctchar(f[0]); + b = unoctchar(f[1]); + c = unoctchar(f[2]); + + if (a < 0 || b < 0 || c < 0) { + /* Invalid escape code, let's take it literal then */ + *(t++) = '\\'; + *(t++) = f[0]; + } else { + *(t++) = (char) ((a << 6) | (b << 3) | c); + f += 2; + } + + break; + } + + case 0: + /* premature end of string.*/ + *(t++) = '\\'; + goto finish; + + default: + /* Invalid escape code, let's take it literal then */ + *(t++) = '\\'; + *(t++) = *f; + break; + } + } + +finish: + *t = 0; + return r; +} + +char *cunescape_length(const char *s, size_t length) { + return cunescape_length_with_prefix(s, length, NULL); +} + +char *cunescape(const char *s) { + assert(s); + + return cunescape_length(s, strlen(s)); +} + +char *xescape(const char *s, const char *bad) { + char *r, *t; + const char *f; + + /* Escapes all chars in bad, in addition to \ and all special + * chars, in \xFF style escaping. May be reversed with + * cunescape. */ + + r = new(char, strlen(s) * 4 + 1); + if (!r) + return NULL; + + for (f = s, t = r; *f; f++) { + + if ((*f < ' ') || (*f >= 127) || + (*f == '\\') || strchr(bad, *f)) { + *(t++) = '\\'; + *(t++) = 'x'; + *(t++) = hexchar(*f >> 4); + *(t++) = hexchar(*f); + } else + *(t++) = *f; + } + + *t = 0; + + return r; +} + +char *bus_path_escape(const char *s) { + char *r, *t; + const char *f; + + assert(s); + + /* Escapes all chars that D-Bus' object path cannot deal + * with. Can be reverse with bus_path_unescape(). We special + * case the empty string. */ + + if (*s == 0) + return strdup("_"); + + r = new(char, strlen(s)*3 + 1); + if (!r) + return NULL; + + for (f = s, t = r; *f; f++) { + + /* Escape everything that is not a-zA-Z0-9. We also + * escape 0-9 if it's the first character */ + + if (!(*f >= 'A' && *f <= 'Z') && + !(*f >= 'a' && *f <= 'z') && + !(f > s && *f >= '0' && *f <= '9')) { + *(t++) = '_'; + *(t++) = hexchar(*f >> 4); + *(t++) = hexchar(*f); + } else + *(t++) = *f; + } + + *t = 0; + + return r; +} + +char *bus_path_unescape(const char *f) { + char *r, *t; + + assert(f); + + /* Special case for the empty string */ + if (streq(f, "_")) + return strdup(""); + + r = new(char, strlen(f) + 1); + if (!r) + return NULL; + + for (t = r; *f; f++) { + + if (*f == '_') { + int a, b; + + if ((a = unhexchar(f[1])) < 0 || + (b = unhexchar(f[2])) < 0) { + /* Invalid escape code, let's take it literal then */ + *(t++) = '_'; + } else { + *(t++) = (char) ((a << 4) | b); + f += 2; + } + } else + *(t++) = *f; + } + + *t = 0; + + return r; +} + +char *ascii_strlower(char *t) { + char *p; + + assert(t); + + for (p = t; *p; p++) + if (*p >= 'A' && *p <= 'Z') + *p = *p - 'A' + 'a'; + + return t; +} + +_pure_ static bool ignore_file_allow_backup(const char *filename) { + assert(filename); + + return + filename[0] == '.' || + streq(filename, "lost+found") || + streq(filename, "aquota.user") || + streq(filename, "aquota.group") || + endswith(filename, ".rpmnew") || + endswith(filename, ".rpmsave") || + endswith(filename, ".rpmorig") || + endswith(filename, ".dpkg-old") || + endswith(filename, ".dpkg-new") || + endswith(filename, ".swp"); +} + +bool ignore_file(const char *filename) { + assert(filename); + + if (endswith(filename, "~")) + return false; + + return ignore_file_allow_backup(filename); +} + +int fd_nonblock(int fd, bool nonblock) { + int flags; + + assert(fd >= 0); + + if ((flags = fcntl(fd, F_GETFL, 0)) < 0) + return -errno; + + if (nonblock) + flags |= O_NONBLOCK; + else + flags &= ~O_NONBLOCK; + + if (fcntl(fd, F_SETFL, flags) < 0) + return -errno; + + return 0; +} + +int fd_cloexec(int fd, bool cloexec) { + int flags; + + assert(fd >= 0); + + if ((flags = fcntl(fd, F_GETFD, 0)) < 0) + return -errno; + + if (cloexec) + flags |= FD_CLOEXEC; + else + flags &= ~FD_CLOEXEC; + + if (fcntl(fd, F_SETFD, flags) < 0) + return -errno; + + return 0; +} + +_pure_ static bool fd_in_set(int fd, const int fdset[], unsigned n_fdset) { + unsigned i; + + assert(n_fdset == 0 || fdset); + + for (i = 0; i < n_fdset; i++) + if (fdset[i] == fd) + return true; + + return false; +} + +int close_all_fds(const int except[], unsigned n_except) { + DIR *d; + struct dirent *de; + int r = 0; + + assert(n_except == 0 || except); + + d = opendir("/proc/self/fd"); + if (!d) { + int fd; + struct rlimit rl; + + /* When /proc isn't available (for example in chroots) + * the fallback is brute forcing through the fd + * table */ + + assert_se(getrlimit(RLIMIT_NOFILE, &rl) >= 0); + for (fd = 3; fd < (int) rl.rlim_max; fd ++) { + + if (fd_in_set(fd, except, n_except)) + continue; + + if (close_nointr(fd) < 0) + if (errno != EBADF && r == 0) + r = -errno; + } + + return r; + } + + while ((de = readdir(d))) { + int fd = -1; + + if (ignore_file(de->d_name)) + continue; + + if (safe_atoi(de->d_name, &fd) < 0) + /* Let's better ignore this, just in case */ + continue; + + if (fd < 3) + continue; + + if (fd == dirfd(d)) + continue; + + if (fd_in_set(fd, except, n_except)) + continue; + + if (close_nointr(fd) < 0) { + /* Valgrind has its own FD and doesn't want to have it closed */ + if (errno != EBADF && r == 0) + r = -errno; + } + } + + closedir(d); + return r; +} + +bool chars_intersect(const char *a, const char *b) { + const char *p; + + /* Returns true if any of the chars in a are in b. */ + for (p = a; *p; p++) + if (strchr(b, *p)) + return true; + + return false; +} + +bool fstype_is_network(const char *fstype) { + static const char table[] = + "cifs\0" + "smbfs\0" + "ncpfs\0" + "nfs\0" + "nfs4\0" + "gfs\0" + "gfs2\0"; + + return nulstr_contains(table, fstype); +} + +int chvt(int vt) { + _cleanup_close_ int fd; + + fd = open_terminal("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC); + if (fd < 0) + return -errno; + + if (vt < 0) { + int tiocl[2] = { + TIOCL_GETKMSGREDIRECT, + 0 + }; + + if (ioctl(fd, TIOCLINUX, tiocl) < 0) + return -errno; + + vt = tiocl[0] <= 0 ? 1 : tiocl[0]; + } + + if (ioctl(fd, VT_ACTIVATE, vt) < 0) + return -errno; + + return 0; +} + +int read_one_char(FILE *f, char *ret, usec_t t, bool *need_nl) { + struct termios old_termios, new_termios; + char c; + char line[LINE_MAX]; + + assert(f); + assert(ret); + + if (tcgetattr(fileno(f), &old_termios) >= 0) { + new_termios = old_termios; + + new_termios.c_lflag &= ~ICANON; + new_termios.c_cc[VMIN] = 1; + new_termios.c_cc[VTIME] = 0; + + if (tcsetattr(fileno(f), TCSADRAIN, &new_termios) >= 0) { + size_t k; + + if (t != (usec_t) -1) { + if (fd_wait_for_event(fileno(f), POLLIN, t) <= 0) { + tcsetattr(fileno(f), TCSADRAIN, &old_termios); + return -ETIMEDOUT; + } + } + + k = fread(&c, 1, 1, f); + + tcsetattr(fileno(f), TCSADRAIN, &old_termios); + + if (k <= 0) + return -EIO; + + if (need_nl) + *need_nl = c != '\n'; + + *ret = c; + return 0; + } + } + + if (t != (usec_t) -1) + if (fd_wait_for_event(fileno(f), POLLIN, t) <= 0) + return -ETIMEDOUT; + + if (!fgets(line, sizeof(line), f)) + return -EIO; + + truncate_nl(line); + + if (strlen(line) != 1) + return -EBADMSG; + + if (need_nl) + *need_nl = false; + + *ret = line[0]; + return 0; +} + +int ask(char *ret, const char *replies, const char *text, ...) { + + assert(ret); + assert(replies); + assert(text); + + for (;;) { + va_list ap; + char c; + int r; + bool need_nl = true; + + if (on_tty()) + fputs(ANSI_HIGHLIGHT_ON, stdout); + + va_start(ap, text); + vprintf(text, ap); + va_end(ap); + + if (on_tty()) + fputs(ANSI_HIGHLIGHT_OFF, stdout); + + fflush(stdout); + + r = read_one_char(stdin, &c, (usec_t) -1, &need_nl); + if (r < 0) { + + if (r == -EBADMSG) { + puts("Bad input, please try again."); + continue; + } + + putchar('\n'); + return r; + } + + if (need_nl) + putchar('\n'); + + if (strchr(replies, c)) { + *ret = c; + return 0; + } + + puts("Read unexpected character, please try again."); + } +} + +int reset_terminal_fd(int fd, bool switch_to_text) { + struct termios termios; + int r = 0; + + /* Set terminal to some sane defaults */ + + assert(fd >= 0); + + /* We leave locked terminal attributes untouched, so that + * Plymouth may set whatever it wants to set, and we don't + * interfere with that. */ + + /* Disable exclusive mode, just in case */ + ioctl(fd, TIOCNXCL); + + /* Switch to text mode */ + if (switch_to_text) + ioctl(fd, KDSETMODE, KD_TEXT); + + /* Enable console unicode mode */ + ioctl(fd, KDSKBMODE, K_UNICODE); + + if (tcgetattr(fd, &termios) < 0) { + r = -errno; + goto finish; + } + + /* We only reset the stuff that matters to the software. How + * hardware is set up we don't touch assuming that somebody + * else will do that for us */ + + termios.c_iflag &= ~(IGNBRK | BRKINT | ISTRIP | INLCR | IGNCR | IUCLC); + termios.c_iflag |= ICRNL | IMAXBEL | IUTF8; + termios.c_oflag |= ONLCR; + termios.c_cflag |= CREAD; + termios.c_lflag = ISIG | ICANON | IEXTEN | ECHO | ECHOE | ECHOK | ECHOCTL | ECHOPRT | ECHOKE; + + termios.c_cc[VINTR] = 03; /* ^C */ + termios.c_cc[VQUIT] = 034; /* ^\ */ + termios.c_cc[VERASE] = 0177; + termios.c_cc[VKILL] = 025; /* ^X */ + termios.c_cc[VEOF] = 04; /* ^D */ + termios.c_cc[VSTART] = 021; /* ^Q */ + termios.c_cc[VSTOP] = 023; /* ^S */ + termios.c_cc[VSUSP] = 032; /* ^Z */ + termios.c_cc[VLNEXT] = 026; /* ^V */ + termios.c_cc[VWERASE] = 027; /* ^W */ + termios.c_cc[VREPRINT] = 022; /* ^R */ + termios.c_cc[VEOL] = 0; + termios.c_cc[VEOL2] = 0; + + termios.c_cc[VTIME] = 0; + termios.c_cc[VMIN] = 1; + + if (tcsetattr(fd, TCSANOW, &termios) < 0) + r = -errno; + +finish: + /* Just in case, flush all crap out */ + tcflush(fd, TCIOFLUSH); + + return r; +} + +int reset_terminal(const char *name) { + int fd, r; + + fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC); + if (fd < 0) + return fd; + + r = reset_terminal_fd(fd, true); + close_nointr_nofail(fd); + + return r; +} + +int open_terminal(const char *name, int mode) { + int fd, r; + unsigned c = 0; + + /* + * If a TTY is in the process of being closed opening it might + * cause EIO. This is horribly awful, but unlikely to be + * changed in the kernel. Hence we work around this problem by + * retrying a couple of times. + * + * https://bugs.launchpad.net/ubuntu/+source/linux/+bug/554172/comments/245 + */ + + for (;;) { + fd = open(name, mode); + if (fd >= 0) + break; + + if (errno != EIO) + return -errno; + + /* Max 1s in total */ + if (c >= 20) + return -errno; + + usleep(50 * USEC_PER_MSEC); + c++; + } + + if (fd < 0) + return -errno; + + r = isatty(fd); + if (r < 0) { + close_nointr_nofail(fd); + return -errno; + } + + if (!r) { + close_nointr_nofail(fd); + return -ENOTTY; + } + + return fd; +} + +int flush_fd(int fd) { + struct pollfd pollfd = { + .fd = fd, + .events = POLLIN, + }; + + for (;;) { + char buf[LINE_MAX]; + ssize_t l; + int r; + + r = poll(&pollfd, 1, 0); + if (r < 0) { + if (errno == EINTR) + continue; + + return -errno; + + } else if (r == 0) + return 0; + + l = read(fd, buf, sizeof(buf)); + if (l < 0) { + + if (errno == EINTR) + continue; + + if (errno == EAGAIN) + return 0; + + return -errno; + } else if (l == 0) + return 0; + } +} + +int acquire_terminal( + const char *name, + bool fail, + bool force, + bool ignore_tiocstty_eperm, + usec_t timeout) { + + int fd = -1, notify = -1, r = 0, wd = -1; + usec_t ts = 0; + + assert(name); + + /* We use inotify to be notified when the tty is closed. We + * create the watch before checking if we can actually acquire + * it, so that we don't lose any event. + * + * Note: strictly speaking this actually watches for the + * device being closed, it does *not* really watch whether a + * tty loses its controlling process. However, unless some + * rogue process uses TIOCNOTTY on /dev/tty *after* closing + * its tty otherwise this will not become a problem. As long + * as the administrator makes sure not configure any service + * on the same tty as an untrusted user this should not be a + * problem. (Which he probably should not do anyway.) */ + + if (timeout != (usec_t) -1) + ts = now(CLOCK_MONOTONIC); + + if (!fail && !force) { + notify = inotify_init1(IN_CLOEXEC | (timeout != (usec_t) -1 ? IN_NONBLOCK : 0)); + if (notify < 0) { + r = -errno; + goto fail; + } + + wd = inotify_add_watch(notify, name, IN_CLOSE); + if (wd < 0) { + r = -errno; + goto fail; + } + } + + for (;;) { + struct sigaction sa_old, sa_new = { + .sa_handler = SIG_IGN, + .sa_flags = SA_RESTART, + }; + + if (notify >= 0) { + r = flush_fd(notify); + if (r < 0) + goto fail; + } + + /* We pass here O_NOCTTY only so that we can check the return + * value TIOCSCTTY and have a reliable way to figure out if we + * successfully became the controlling process of the tty */ + fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC); + if (fd < 0) + return fd; + + /* Temporarily ignore SIGHUP, so that we don't get SIGHUP'ed + * if we already own the tty. */ + assert_se(sigaction(SIGHUP, &sa_new, &sa_old) == 0); + + /* First, try to get the tty */ + if (ioctl(fd, TIOCSCTTY, force) < 0) + r = -errno; + + assert_se(sigaction(SIGHUP, &sa_old, NULL) == 0); + + /* Sometimes it makes sense to ignore TIOCSCTTY + * returning EPERM, i.e. when very likely we already + * are have this controlling terminal. */ + if (r < 0 && r == -EPERM && ignore_tiocstty_eperm) + r = 0; + + if (r < 0 && (force || fail || r != -EPERM)) { + goto fail; + } + + if (r >= 0) + break; + + assert(!fail); + assert(!force); + assert(notify >= 0); + + for (;;) { + uint8_t inotify_buffer[sizeof(struct inotify_event) + FILENAME_MAX]; + ssize_t l; + struct inotify_event *e; + + if (timeout != (usec_t) -1) { + usec_t n; + + n = now(CLOCK_MONOTONIC); + if (ts + timeout < n) { + r = -ETIMEDOUT; + goto fail; + } + + r = fd_wait_for_event(fd, POLLIN, ts + timeout - n); + if (r < 0) + goto fail; + + if (r == 0) { + r = -ETIMEDOUT; + goto fail; + } + } + + l = read(notify, inotify_buffer, sizeof(inotify_buffer)); + if (l < 0) { + + if (errno == EINTR || errno == EAGAIN) + continue; + + r = -errno; + goto fail; + } + + e = (struct inotify_event*) inotify_buffer; + + while (l > 0) { + size_t step; + + if (e->wd != wd || !(e->mask & IN_CLOSE)) { + r = -EIO; + goto fail; + } + + step = sizeof(struct inotify_event) + e->len; + assert(step <= (size_t) l); + + e = (struct inotify_event*) ((uint8_t*) e + step); + l -= step; + } + + break; + } + + /* We close the tty fd here since if the old session + * ended our handle will be dead. It's important that + * we do this after sleeping, so that we don't enter + * an endless loop. */ + close_nointr_nofail(fd); + } + + if (notify >= 0) + close_nointr_nofail(notify); + + r = reset_terminal_fd(fd, true); + if (r < 0) + log_warning("Failed to reset terminal: %s", strerror(-r)); + + return fd; + +fail: + if (fd >= 0) + close_nointr_nofail(fd); + + if (notify >= 0) + close_nointr_nofail(notify); + + return r; +} + +int release_terminal(void) { + int r = 0; + struct sigaction sa_old, sa_new = { + .sa_handler = SIG_IGN, + .sa_flags = SA_RESTART, + }; + _cleanup_close_ int fd; + + fd = open("/dev/tty", O_RDWR|O_NOCTTY|O_NDELAY|O_CLOEXEC); + if (fd < 0) + return -errno; + + /* Temporarily ignore SIGHUP, so that we don't get SIGHUP'ed + * by our own TIOCNOTTY */ + assert_se(sigaction(SIGHUP, &sa_new, &sa_old) == 0); + + if (ioctl(fd, TIOCNOTTY) < 0) + r = -errno; + + assert_se(sigaction(SIGHUP, &sa_old, NULL) == 0); + + return r; +} + +int sigaction_many(const struct sigaction *sa, ...) { + va_list ap; + int r = 0, sig; + + va_start(ap, sa); + while ((sig = va_arg(ap, int)) > 0) + if (sigaction(sig, sa, NULL) < 0) + r = -errno; + va_end(ap); + + return r; +} + +int ignore_signals(int sig, ...) { + struct sigaction sa = { + .sa_handler = SIG_IGN, + .sa_flags = SA_RESTART, + }; + va_list ap; + int r = 0; + + + if (sigaction(sig, &sa, NULL) < 0) + r = -errno; + + va_start(ap, sig); + while ((sig = va_arg(ap, int)) > 0) + if (sigaction(sig, &sa, NULL) < 0) + r = -errno; + va_end(ap); + + return r; +} + +int default_signals(int sig, ...) { + struct sigaction sa = { + .sa_handler = SIG_DFL, + .sa_flags = SA_RESTART, + }; + va_list ap; + int r = 0; + + if (sigaction(sig, &sa, NULL) < 0) + r = -errno; + + va_start(ap, sig); + while ((sig = va_arg(ap, int)) > 0) + if (sigaction(sig, &sa, NULL) < 0) + r = -errno; + va_end(ap); + + return r; +} + +int close_pipe(int p[]) { + int a = 0, b = 0; + + assert(p); + + if (p[0] >= 0) { + a = close_nointr(p[0]); + p[0] = -1; + } + + if (p[1] >= 0) { + b = close_nointr(p[1]); + p[1] = -1; + } + + return a < 0 ? a : b; +} + +ssize_t loop_read(int fd, void *buf, size_t nbytes, bool do_poll) { + uint8_t *p; + ssize_t n = 0; + + assert(fd >= 0); + assert(buf); + + p = buf; + + while (nbytes > 0) { + ssize_t k; + + if ((k = read(fd, p, nbytes)) <= 0) { + + if (k < 0 && errno == EINTR) + continue; + + if (k < 0 && errno == EAGAIN && do_poll) { + struct pollfd pollfd = { + .fd = fd, + .events = POLLIN, + }; + + if (poll(&pollfd, 1, -1) < 0) { + if (errno == EINTR) + continue; + + return n > 0 ? n : -errno; + } + + if (pollfd.revents != POLLIN) + return n > 0 ? n : -EIO; + + continue; + } + + return n > 0 ? n : (k < 0 ? -errno : 0); + } + + p += k; + nbytes -= k; + n += k; + } + + return n; +} + +ssize_t loop_write(int fd, const void *buf, size_t nbytes, bool do_poll) { + const uint8_t *p; + ssize_t n = 0; + + assert(fd >= 0); + assert(buf); + + p = buf; + + while (nbytes > 0) { + ssize_t k; + + k = write(fd, p, nbytes); + if (k <= 0) { + + if (k < 0 && errno == EINTR) + continue; + + if (k < 0 && errno == EAGAIN && do_poll) { + struct pollfd pollfd = { + .fd = fd, + .events = POLLOUT, + }; + + if (poll(&pollfd, 1, -1) < 0) { + if (errno == EINTR) + continue; + + return n > 0 ? n : -errno; + } + + if (pollfd.revents != POLLOUT) + return n > 0 ? n : -EIO; + + continue; + } + + return n > 0 ? n : (k < 0 ? -errno : 0); + } + + p += k; + nbytes -= k; + n += k; + } + + return n; +} + +int parse_bytes(const char *t, off_t *bytes) { + static const struct { + const char *suffix; + off_t factor; + } table[] = { + { "B", 1 }, + { "K", 1024ULL }, + { "M", 1024ULL*1024ULL }, + { "G", 1024ULL*1024ULL*1024ULL }, + { "T", 1024ULL*1024ULL*1024ULL*1024ULL }, + { "P", 1024ULL*1024ULL*1024ULL*1024ULL*1024ULL }, + { "E", 1024ULL*1024ULL*1024ULL*1024ULL*1024ULL*1024ULL }, + { "", 1 }, + }; + + const char *p; + off_t r = 0; + + assert(t); + assert(bytes); + + p = t; + do { + long long l; + char *e; + unsigned i; + + errno = 0; + l = strtoll(p, &e, 10); + + if (errno > 0) + return -errno; + + if (l < 0) + return -ERANGE; + + if (e == p) + return -EINVAL; + + e += strspn(e, WHITESPACE); + + for (i = 0; i < ELEMENTSOF(table); i++) + if (startswith(e, table[i].suffix)) { + r += (off_t) l * table[i].factor; + p = e + strlen(table[i].suffix); + break; + } + + if (i >= ELEMENTSOF(table)) + return -EINVAL; + + } while (*p != 0); + + *bytes = r; + + return 0; +} + +int make_stdio(int fd) { + int r, s, t; + + assert(fd >= 0); + + r = dup3(fd, STDIN_FILENO, 0); + s = dup3(fd, STDOUT_FILENO, 0); + t = dup3(fd, STDERR_FILENO, 0); + + if (fd >= 3) + close_nointr_nofail(fd); + + if (r < 0 || s < 0 || t < 0) + return -errno; + + /* We rely here that the new fd has O_CLOEXEC not set */ + + return 0; +} + +int make_null_stdio(void) { + int null_fd; + + null_fd = open("/dev/null", O_RDWR|O_NOCTTY); + if (null_fd < 0) + return -errno; + + return make_stdio(null_fd); +} + +bool is_device_path(const char *path) { + + /* Returns true on paths that refer to a device, either in + * sysfs or in /dev */ + + return + path_startswith(path, "/dev/") || + path_startswith(path, "/sys/"); +} + +int dir_is_empty(const char *path) { + _cleanup_closedir_ DIR *d; + int r; + + d = opendir(path); + if (!d) + return -errno; + + for (;;) { + struct dirent *de; + union dirent_storage buf; + + r = readdir_r(d, &buf.de, &de); + if (r > 0) + return -r; + + if (!de) + return 1; + + if (!ignore_file(de->d_name)) + return 0; + } +} + +char* dirname_malloc(const char *path) { + char *d, *dir, *dir2; + + d = strdup(path); + if (!d) + return NULL; + dir = dirname(d); + assert(dir); + + if (dir != d) { + dir2 = strdup(dir); + free(d); + return dir2; + } + + return dir; +} + +unsigned long long random_ull(void) { + _cleanup_close_ int fd; + uint64_t ull; + ssize_t r; + + fd = open("/dev/urandom", O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (fd < 0) + goto fallback; + + r = loop_read(fd, &ull, sizeof(ull), true); + if (r != sizeof(ull)) + goto fallback; + + return ull; + +fallback: + return random() * RAND_MAX + random(); +} + +void rename_process(const char name[8]) { + assert(name); + + /* This is a like a poor man's setproctitle(). It changes the + * comm field, argv[0], and also the glibc's internally used + * name of the process. For the first one a limit of 16 chars + * applies, to the second one usually one of 10 (i.e. length + * of "/sbin/init"), to the third one one of 7 (i.e. length of + * "systemd"). If you pass a longer string it will be + * truncated */ + + prctl(PR_SET_NAME, name); + + if (program_invocation_name) + strncpy(program_invocation_name, name, strlen(program_invocation_name)); + + if (saved_argc > 0) { + int i; + + if (saved_argv[0]) + strncpy(saved_argv[0], name, strlen(saved_argv[0])); + + for (i = 1; i < saved_argc; i++) { + if (!saved_argv[i]) + break; + + memset(saved_argv[i], 0, strlen(saved_argv[i])); + } + } +} + +void sigset_add_many(sigset_t *ss, ...) { + va_list ap; + int sig; + + assert(ss); + + va_start(ap, ss); + while ((sig = va_arg(ap, int)) > 0) + assert_se(sigaddset(ss, sig) == 0); + va_end(ap); +} + +char* gethostname_malloc(void) { + struct utsname u; + + assert_se(uname(&u) >= 0); + + if (!isempty(u.nodename) && !streq(u.nodename, "(none)")) + return strdup(u.nodename); + + return strdup(u.sysname); +} + +bool hostname_is_set(void) { + struct utsname u; + + assert_se(uname(&u) >= 0); + + return !isempty(u.nodename) && !streq(u.nodename, "(none)"); +} + +static char *lookup_uid(uid_t uid) { + long bufsize; + char *name; + _cleanup_free_ char *buf = NULL; + struct passwd pwbuf, *pw = NULL; + + /* Shortcut things to avoid NSS lookups */ + if (uid == 0) + return strdup("root"); + + bufsize = sysconf(_SC_GETPW_R_SIZE_MAX); + if (bufsize <= 0) + bufsize = 4096; + + buf = malloc(bufsize); + if (!buf) + return NULL; + + if (getpwuid_r(uid, &pwbuf, buf, bufsize, &pw) == 0 && pw) + return strdup(pw->pw_name); + + if (asprintf(&name, "%lu", (unsigned long) uid) < 0) + return NULL; + + return name; +} + +char* getlogname_malloc(void) { + uid_t uid; + struct stat st; + + if (isatty(STDIN_FILENO) && fstat(STDIN_FILENO, &st) >= 0) + uid = st.st_uid; + else + uid = getuid(); + + return lookup_uid(uid); +} + +char *getusername_malloc(void) { + const char *e; + + e = getenv("USER"); + if (e) + return strdup(e); + + return lookup_uid(getuid()); +} + +int getttyname_malloc(int fd, char **r) { + char path[PATH_MAX], *c; + int k; + + assert(r); + + k = ttyname_r(fd, path, sizeof(path)); + if (k != 0) + return -k; + + char_array_0(path); + + c = strdup(startswith(path, "/dev/") ? path + 5 : path); + if (!c) + return -ENOMEM; + + *r = c; + return 0; +} + +int getttyname_harder(int fd, char **r) { + int k; + char *s; + + k = getttyname_malloc(fd, &s); + if (k < 0) + return k; + + if (streq(s, "tty")) { + free(s); + return get_ctty(0, NULL, r); + } + + *r = s; + return 0; +} + +int get_ctty_devnr(pid_t pid, dev_t *d) { + _cleanup_fclose_ FILE *f = NULL; + char line[LINE_MAX], *p; + unsigned long ttynr; + const char *fn; + int k; + + assert(pid >= 0); + assert(d); + + if (pid == 0) + fn = "/proc/self/stat"; + else + fn = procfs_file_alloca(pid, "stat"); + + f = fopen(fn, "re"); + if (!f) + return -errno; + + if (!fgets(line, sizeof(line), f)) { + k = feof(f) ? -EIO : -errno; + return k; + } + + p = strrchr(line, ')'); + if (!p) + return -EIO; + + p++; + + if (sscanf(p, " " + "%*c " /* state */ + "%*d " /* ppid */ + "%*d " /* pgrp */ + "%*d " /* session */ + "%lu ", /* ttynr */ + &ttynr) != 1) + return -EIO; + + if (major(ttynr) == 0 && minor(ttynr) == 0) + return -ENOENT; + + *d = (dev_t) ttynr; + return 0; +} + +int get_ctty(pid_t pid, dev_t *_devnr, char **r) { + int k; + char fn[sizeof("/dev/char/")-1 + 2*DECIMAL_STR_MAX(unsigned) + 1 + 1], *s, *b, *p; + dev_t devnr; + + assert(r); + + k = get_ctty_devnr(pid, &devnr); + if (k < 0) + return k; + + snprintf(fn, sizeof(fn), "/dev/char/%u:%u", major(devnr), minor(devnr)); + + k = readlink_malloc(fn, &s); + if (k < 0) { + + if (k != -ENOENT) + return k; + + /* This is an ugly hack */ + if (major(devnr) == 136) { + if (asprintf(&b, "pts/%lu", (unsigned long) minor(devnr)) < 0) + return -ENOMEM; + + *r = b; + if (_devnr) + *_devnr = devnr; + + return 0; + } + + /* Probably something like the ptys which have no + * symlink in /dev/char. Let's return something + * vaguely useful. */ + + b = strdup(fn + 5); + if (!b) + return -ENOMEM; + + *r = b; + if (_devnr) + *_devnr = devnr; + + return 0; + } + + if (startswith(s, "/dev/")) + p = s + 5; + else if (startswith(s, "../")) + p = s + 3; + else + p = s; + + b = strdup(p); + free(s); + + if (!b) + return -ENOMEM; + + *r = b; + if (_devnr) + *_devnr = devnr; + + return 0; +} + +int rm_rf_children_dangerous(int fd, bool only_dirs, bool honour_sticky, struct stat *root_dev) { + DIR *d; + int ret = 0; + + assert(fd >= 0); + + /* This returns the first error we run into, but nevertheless + * tries to go on. This closes the passed fd. */ + + d = fdopendir(fd); + if (!d) { + close_nointr_nofail(fd); + + return errno == ENOENT ? 0 : -errno; + } + + for (;;) { + struct dirent *de; + union dirent_storage buf; + bool is_dir, keep_around; + struct stat st; + int r; + + r = readdir_r(d, &buf.de, &de); + if (r != 0 && ret == 0) { + ret = -r; + break; + } + + if (!de) + break; + + if (streq(de->d_name, ".") || streq(de->d_name, "..")) + continue; + + if (de->d_type == DT_UNKNOWN || + honour_sticky || + (de->d_type == DT_DIR && root_dev)) { + if (fstatat(fd, de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) { + if (ret == 0 && errno != ENOENT) + ret = -errno; + continue; + } + + is_dir = S_ISDIR(st.st_mode); + keep_around = + honour_sticky && + (st.st_uid == 0 || st.st_uid == getuid()) && + (st.st_mode & S_ISVTX); + } else { + is_dir = de->d_type == DT_DIR; + keep_around = false; + } + + if (is_dir) { + int subdir_fd; + + /* if root_dev is set, remove subdirectories only, if device is same as dir */ + if (root_dev && st.st_dev != root_dev->st_dev) + continue; + + subdir_fd = openat(fd, de->d_name, + O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME); + if (subdir_fd < 0) { + if (ret == 0 && errno != ENOENT) + ret = -errno; + continue; + } + + r = rm_rf_children_dangerous(subdir_fd, only_dirs, honour_sticky, root_dev); + if (r < 0 && ret == 0) + ret = r; + + if (!keep_around) + if (unlinkat(fd, de->d_name, AT_REMOVEDIR) < 0) { + if (ret == 0 && errno != ENOENT) + ret = -errno; + } + + } else if (!only_dirs && !keep_around) { + + if (unlinkat(fd, de->d_name, 0) < 0) { + if (ret == 0 && errno != ENOENT) + ret = -errno; + } + } + } + + closedir(d); + + return ret; +} + +_pure_ static int is_temporary_fs(struct statfs *s) { + assert(s); + return + F_TYPE_CMP(s->f_type, TMPFS_MAGIC) || + F_TYPE_CMP(s->f_type, RAMFS_MAGIC); +} + +int rm_rf_children(int fd, bool only_dirs, bool honour_sticky, struct stat *root_dev) { + struct statfs s; + + assert(fd >= 0); + + if (fstatfs(fd, &s) < 0) { + close_nointr_nofail(fd); + return -errno; + } + + /* We refuse to clean disk file systems with this call. This + * is extra paranoia just to be sure we never ever remove + * non-state data */ + if (!is_temporary_fs(&s)) { + log_error("Attempted to remove disk file system, and we can't allow that."); + close_nointr_nofail(fd); + return -EPERM; + } + + return rm_rf_children_dangerous(fd, only_dirs, honour_sticky, root_dev); +} + +static int rm_rf_internal(const char *path, bool only_dirs, bool delete_root, bool honour_sticky, bool dangerous) { + int fd, r; + struct statfs s; + + assert(path); + + /* We refuse to clean the root file system with this + * call. This is extra paranoia to never cause a really + * seriously broken system. */ + if (path_equal(path, "/")) { + log_error("Attempted to remove entire root file system, and we can't allow that."); + return -EPERM; + } + + fd = open(path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME); + if (fd < 0) { + + if (errno != ENOTDIR) + return -errno; + + if (!dangerous) { + if (statfs(path, &s) < 0) + return -errno; + + if (!is_temporary_fs(&s)) { + log_error("Attempted to remove disk file system, and we can't allow that."); + return -EPERM; + } + } + + if (delete_root && !only_dirs) + if (unlink(path) < 0 && errno != ENOENT) + return -errno; + + return 0; + } + + if (!dangerous) { + if (fstatfs(fd, &s) < 0) { + close_nointr_nofail(fd); + return -errno; + } + + if (!is_temporary_fs(&s)) { + log_error("Attempted to remove disk file system, and we can't allow that."); + close_nointr_nofail(fd); + return -EPERM; + } + } + + r = rm_rf_children_dangerous(fd, only_dirs, honour_sticky, NULL); + if (delete_root) { + + if (honour_sticky && file_is_priv_sticky(path) > 0) + return r; + + if (rmdir(path) < 0 && errno != ENOENT) { + if (r == 0) + r = -errno; + } + } + + return r; +} + +int rm_rf(const char *path, bool only_dirs, bool delete_root, bool honour_sticky) { + return rm_rf_internal(path, only_dirs, delete_root, honour_sticky, false); +} + +int rm_rf_dangerous(const char *path, bool only_dirs, bool delete_root, bool honour_sticky) { + return rm_rf_internal(path, only_dirs, delete_root, honour_sticky, true); +} + +int chmod_and_chown(const char *path, mode_t mode, uid_t uid, gid_t gid) { + assert(path); + + /* Under the assumption that we are running privileged we + * first change the access mode and only then hand out + * ownership to avoid a window where access is too open. */ + + if (mode != (mode_t) -1) + if (chmod(path, mode) < 0) + return -errno; + + if (uid != (uid_t) -1 || gid != (gid_t) -1) + if (chown(path, uid, gid) < 0) + return -errno; + + return 0; +} + +int fchmod_and_fchown(int fd, mode_t mode, uid_t uid, gid_t gid) { + assert(fd >= 0); + + /* Under the assumption that we are running privileged we + * first change the access mode and only then hand out + * ownership to avoid a window where access is too open. */ + + if (fchmod(fd, mode) < 0) + return -errno; + + if (fchown(fd, uid, gid) < 0) + return -errno; + + return 0; +} + +cpu_set_t* cpu_set_malloc(unsigned *ncpus) { + cpu_set_t *r; + unsigned n = 1024; + + /* Allocates the cpuset in the right size */ + + for (;;) { + if (!(r = CPU_ALLOC(n))) + return NULL; + + if (sched_getaffinity(0, CPU_ALLOC_SIZE(n), r) >= 0) { + CPU_ZERO_S(CPU_ALLOC_SIZE(n), r); + + if (ncpus) + *ncpus = n; + + return r; + } + + CPU_FREE(r); + + if (errno != EINVAL) + return NULL; + + n *= 2; + } +} + +int status_vprintf(const char *status, bool ellipse, bool ephemeral, const char *format, va_list ap) { + static const char status_indent[] = " "; /* "[" STATUS "] " */ + _cleanup_free_ char *s = NULL; + _cleanup_close_ int fd = -1; + struct iovec iovec[6] = {}; + int n = 0; + static bool prev_ephemeral; + + assert(format); + + /* This is independent of logging, as status messages are + * optional and go exclusively to the console. */ + + if (vasprintf(&s, format, ap) < 0) + return log_oom(); + + fd = open_terminal("/dev/console", O_WRONLY|O_NOCTTY|O_CLOEXEC); + if (fd < 0) + return fd; + + if (ellipse) { + char *e; + size_t emax, sl; + int c; + + c = fd_columns(fd); + if (c <= 0) + c = 80; + + sl = status ? sizeof(status_indent)-1 : 0; + + emax = c - sl - 1; + if (emax < 3) + emax = 3; + + e = ellipsize(s, emax, 75); + if (e) { + free(s); + s = e; + } + } + + if (prev_ephemeral) + IOVEC_SET_STRING(iovec[n++], "\r" ANSI_ERASE_TO_END_OF_LINE); + prev_ephemeral = ephemeral; + + if (status) { + if (!isempty(status)) { + IOVEC_SET_STRING(iovec[n++], "["); + IOVEC_SET_STRING(iovec[n++], status); + IOVEC_SET_STRING(iovec[n++], "] "); + } else + IOVEC_SET_STRING(iovec[n++], status_indent); + } + + IOVEC_SET_STRING(iovec[n++], s); + if (!ephemeral) + IOVEC_SET_STRING(iovec[n++], "\n"); + + if (writev(fd, iovec, n) < 0) + return -errno; + + return 0; +} + +int status_printf(const char *status, bool ellipse, bool ephemeral, const char *format, ...) { + va_list ap; + int r; + + assert(format); + + va_start(ap, format); + r = status_vprintf(status, ellipse, ephemeral, format, ap); + va_end(ap); + + return r; +} + +int status_welcome(void) { + int r; + _cleanup_free_ char *pretty_name = NULL, *ansi_color = NULL; + + r = parse_env_file("/etc/os-release", NEWLINE, + "PRETTY_NAME", &pretty_name, + "ANSI_COLOR", &ansi_color, + NULL); + if (r < 0 && r != -ENOENT) + log_warning("Failed to read /etc/os-release: %s", strerror(-r)); + + return status_printf(NULL, false, false, + "\nWelcome to \x1B[%sm%s\x1B[0m!\n", + isempty(ansi_color) ? "1" : ansi_color, + isempty(pretty_name) ? "Linux" : pretty_name); +} + +char *replace_env(const char *format, char **env) { + enum { + WORD, + CURLY, + VARIABLE + } state = WORD; + + const char *e, *word = format; + char *r = NULL, *k; + + assert(format); + + for (e = format; *e; e ++) { + + switch (state) { + + case WORD: + if (*e == '$') + state = CURLY; + break; + + case CURLY: + if (*e == '{') { + if (!(k = strnappend(r, word, e-word-1))) + goto fail; + + free(r); + r = k; + + word = e-1; + state = VARIABLE; + + } else if (*e == '$') { + if (!(k = strnappend(r, word, e-word))) + goto fail; + + free(r); + r = k; + + word = e+1; + state = WORD; + } else + state = WORD; + break; + + case VARIABLE: + if (*e == '}') { + const char *t; + + t = strempty(strv_env_get_n(env, word+2, e-word-2)); + + k = strappend(r, t); + if (!k) + goto fail; + + free(r); + r = k; + + word = e+1; + state = WORD; + } + break; + } + } + + if (!(k = strnappend(r, word, e-word))) + goto fail; + + free(r); + return k; + +fail: + free(r); + return NULL; +} + +char **replace_env_argv(char **argv, char **env) { + char **r, **i; + unsigned k = 0, l = 0; + + l = strv_length(argv); + + if (!(r = new(char*, l+1))) + return NULL; + + STRV_FOREACH(i, argv) { + + /* If $FOO appears as single word, replace it by the split up variable */ + if ((*i)[0] == '$' && (*i)[1] != '{') { + char *e; + char **w, **m; + unsigned q; + + e = strv_env_get(env, *i+1); + if (e) { + + if (!(m = strv_split_quoted(e))) { + r[k] = NULL; + strv_free(r); + return NULL; + } + } else + m = NULL; + + q = strv_length(m); + l = l + q - 1; + + if (!(w = realloc(r, sizeof(char*) * (l+1)))) { + r[k] = NULL; + strv_free(r); + strv_free(m); + return NULL; + } + + r = w; + if (m) { + memcpy(r + k, m, q * sizeof(char*)); + free(m); + } + + k += q; + continue; + } + + /* If ${FOO} appears as part of a word, replace it by the variable as-is */ + if (!(r[k++] = replace_env(*i, env))) { + strv_free(r); + return NULL; + } + } + + r[k] = NULL; + return r; +} + +int fd_columns(int fd) { + struct winsize ws = {}; + + if (ioctl(fd, TIOCGWINSZ, &ws) < 0) + return -errno; + + if (ws.ws_col <= 0) + return -EIO; + + return ws.ws_col; +} + +unsigned columns(void) { + const char *e; + int c; + + if (_likely_(cached_columns > 0)) + return cached_columns; + + c = 0; + e = getenv("COLUMNS"); + if (e) + safe_atoi(e, &c); + + if (c <= 0) + c = fd_columns(STDOUT_FILENO); + + if (c <= 0) + c = 80; + + cached_columns = c; + return c; +} + +int fd_lines(int fd) { + struct winsize ws = {}; + + if (ioctl(fd, TIOCGWINSZ, &ws) < 0) + return -errno; + + if (ws.ws_row <= 0) + return -EIO; + + return ws.ws_row; +} + +unsigned lines(void) { + const char *e; + unsigned l; + + if (_likely_(cached_lines > 0)) + return cached_lines; + + l = 0; + e = getenv("LINES"); + if (e) + safe_atou(e, &l); + + if (l <= 0) + l = fd_lines(STDOUT_FILENO); + + if (l <= 0) + l = 24; + + cached_lines = l; + return cached_lines; +} + +/* intended to be used as a SIGWINCH sighandler */ +void columns_lines_cache_reset(int signum) { + cached_columns = 0; + cached_lines = 0; +} + +bool on_tty(void) { + static int cached_on_tty = -1; + + if (_unlikely_(cached_on_tty < 0)) + cached_on_tty = isatty(STDOUT_FILENO) > 0; + + return cached_on_tty; +} + +int running_in_chroot(void) { + struct stat a = {}, b = {}; + + /* Only works as root */ + if (stat("/proc/1/root", &a) < 0) + return -errno; + + if (stat("/", &b) < 0) + return -errno; + + return + a.st_dev != b.st_dev || + a.st_ino != b.st_ino; +} + +char *ellipsize_mem(const char *s, size_t old_length, size_t new_length, unsigned percent) { + size_t x; + char *r; + + assert(s); + assert(percent <= 100); + assert(new_length >= 3); + + if (old_length <= 3 || old_length <= new_length) + return strndup(s, old_length); + + r = new0(char, new_length+1); + if (!r) + return r; + + x = (new_length * percent) / 100; + + if (x > new_length - 3) + x = new_length - 3; + + memcpy(r, s, x); + r[x] = '.'; + r[x+1] = '.'; + r[x+2] = '.'; + memcpy(r + x + 3, + s + old_length - (new_length - x - 3), + new_length - x - 3); + + return r; +} + +char *ellipsize(const char *s, size_t length, unsigned percent) { + return ellipsize_mem(s, strlen(s), length, percent); +} + +int touch(const char *path) { + int fd; + + assert(path); + + /* This just opens the file for writing, ensuring it + * exists. It doesn't call utimensat() the way /usr/bin/touch + * does it. */ + + fd = open(path, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY, 0644); + if (fd < 0) + return -errno; + + close_nointr_nofail(fd); + return 0; +} + +char *unquote(const char *s, const char* quotes) { + size_t l; + assert(s); + + /* This is rather stupid, simply removes the heading and + * trailing quotes if there is one. Doesn't care about + * escaping or anything. We should make this smarter one + * day...*/ + + l = strlen(s); + if (l < 2) + return strdup(s); + + if (strchr(quotes, s[0]) && s[l-1] == s[0]) + return strndup(s+1, l-2); + + return strdup(s); +} + +char *normalize_env_assignment(const char *s) { + _cleanup_free_ char *name = NULL, *value = NULL, *p = NULL; + char *eq, *r; + + eq = strchr(s, '='); + if (!eq) { + char *t; + + r = strdup(s); + if (!r) + return NULL; + + t = strstrip(r); + if (t == r) + return r; + + memmove(r, t, strlen(t) + 1); + return r; + } + + name = strndup(s, eq - s); + if (!name) + return NULL; + + p = strdup(eq + 1); + if (!p) + return NULL; + + value = unquote(strstrip(p), QUOTES); + if (!value) + return NULL; + + if (asprintf(&r, "%s=%s", strstrip(name), value) < 0) + r = NULL; + + return r; +} + +int wait_for_terminate(pid_t pid, siginfo_t *status) { + siginfo_t dummy; + + assert(pid >= 1); + + if (!status) + status = &dummy; + + for (;;) { + zero(*status); + + if (waitid(P_PID, pid, status, WEXITED) < 0) { + + if (errno == EINTR) + continue; + + return -errno; + } + + return 0; + } +} + +int wait_for_terminate_and_warn(const char *name, pid_t pid) { + int r; + siginfo_t status; + + assert(name); + assert(pid > 1); + + r = wait_for_terminate(pid, &status); + if (r < 0) { + log_warning("Failed to wait for %s: %s", name, strerror(-r)); + return r; + } + + if (status.si_code == CLD_EXITED) { + if (status.si_status != 0) { + log_warning("%s failed with error code %i.", name, status.si_status); + return status.si_status; + } + + log_debug("%s succeeded.", name); + return 0; + + } else if (status.si_code == CLD_KILLED || + status.si_code == CLD_DUMPED) { + + log_warning("%s terminated by signal %s.", name, signal_to_string(status.si_status)); + return -EPROTO; + } + + log_warning("%s failed due to unknown reason.", name); + return -EPROTO; +} + +_noreturn_ void freeze(void) { + + /* Make sure nobody waits for us on a socket anymore */ + close_all_fds(NULL, 0); + + sync(); + + for (;;) + pause(); +} + +bool null_or_empty(struct stat *st) { + assert(st); + + if (S_ISREG(st->st_mode) && st->st_size <= 0) + return true; + + if (S_ISCHR(st->st_mode) || S_ISBLK(st->st_mode)) + return true; + + return false; +} + +int null_or_empty_path(const char *fn) { + struct stat st; + + assert(fn); + + if (stat(fn, &st) < 0) + return -errno; + + return null_or_empty(&st); +} + +DIR *xopendirat(int fd, const char *name, int flags) { + int nfd; + DIR *d; + + nfd = openat(fd, name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|flags); + if (nfd < 0) + return NULL; + + d = fdopendir(nfd); + if (!d) { + close_nointr_nofail(nfd); + return NULL; + } + + return d; +} + +int signal_from_string_try_harder(const char *s) { + int signo; + assert(s); + + signo = signal_from_string(s); + if (signo <= 0) + if (startswith(s, "SIG")) + return signal_from_string(s+3); + + return signo; +} + +static char *tag_to_udev_node(const char *tagvalue, const char *by) { + char *dn, *t, *u; + int r; + + /* FIXME: to follow udev's logic 100% we need to leave valid + * UTF8 chars unescaped */ + + u = unquote(tagvalue, "\"\'"); + if (u == NULL) + return NULL; + + t = xescape(u, "/ "); + free(u); + + if (t == NULL) + return NULL; + + r = asprintf(&dn, "/dev/disk/by-%s/%s", by, t); + free(t); + + if (r < 0) + return NULL; + + return dn; +} + +char *fstab_node_to_udev_node(const char *p) { + assert(p); + + if (startswith(p, "LABEL=")) + return tag_to_udev_node(p+6, "label"); + + if (startswith(p, "UUID=")) + return tag_to_udev_node(p+5, "uuid"); + + if (startswith(p, "PARTUUID=")) + return tag_to_udev_node(p+9, "partuuid"); + + if (startswith(p, "PARTLABEL=")) + return tag_to_udev_node(p+10, "partlabel"); + + return strdup(p); +} + +bool tty_is_vc(const char *tty) { + assert(tty); + + if (startswith(tty, "/dev/")) + tty += 5; + + return vtnr_from_tty(tty) >= 0; +} + +bool tty_is_console(const char *tty) { + assert(tty); + + if (startswith(tty, "/dev/")) + tty += 5; + + return streq(tty, "console"); +} + +int vtnr_from_tty(const char *tty) { + int i, r; + + assert(tty); + + if (startswith(tty, "/dev/")) + tty += 5; + + if (!startswith(tty, "tty") ) + return -EINVAL; + + if (tty[3] < '0' || tty[3] > '9') + return -EINVAL; + + r = safe_atoi(tty+3, &i); + if (r < 0) + return r; + + if (i < 0 || i > 63) + return -EINVAL; + + return i; +} + +char *resolve_dev_console(char **active) { + char *tty; + + /* Resolve where /dev/console is pointing to, if /sys is actually ours + * (i.e. not read-only-mounted which is a sign for container setups) */ + + if (path_is_read_only_fs("/sys") > 0) + return NULL; + + if (read_one_line_file("/sys/class/tty/console/active", active) < 0) + return NULL; + + /* If multiple log outputs are configured the last one is what + * /dev/console points to */ + tty = strrchr(*active, ' '); + if (tty) + tty++; + else + tty = *active; + + return tty; +} + +bool tty_is_vc_resolve(const char *tty) { + char *active = NULL; + bool b; + + assert(tty); + + if (startswith(tty, "/dev/")) + tty += 5; + + if (streq(tty, "console")) { + tty = resolve_dev_console(&active); + if (!tty) + return false; + } + + b = tty_is_vc(tty); + free(active); + + return b; +} + +const char *default_term_for_tty(const char *tty) { + assert(tty); + + return tty_is_vc_resolve(tty) ? "TERM=linux" : "TERM=vt102"; +} + +bool dirent_is_file(const struct dirent *de) { + assert(de); + + if (ignore_file(de->d_name)) + return false; + + if (de->d_type != DT_REG && + de->d_type != DT_LNK && + de->d_type != DT_UNKNOWN) + return false; + + return true; +} + +bool dirent_is_file_with_suffix(const struct dirent *de, const char *suffix) { + assert(de); + + if (de->d_type != DT_REG && + de->d_type != DT_LNK && + de->d_type != DT_UNKNOWN) + return false; + + if (ignore_file_allow_backup(de->d_name)) + return false; + + return endswith(de->d_name, suffix); +} + +void execute_directory(const char *directory, DIR *d, char *argv[]) { + DIR *_d = NULL; + struct dirent *de; + Hashmap *pids = NULL; + + assert(directory); + + /* Executes all binaries in a directory in parallel and + * waits for them to finish. */ + + if (!d) { + if (!(_d = opendir(directory))) { + + if (errno == ENOENT) + return; + + log_error("Failed to enumerate directory %s: %m", directory); + return; + } + + d = _d; + } + + if (!(pids = hashmap_new(trivial_hash_func, trivial_compare_func))) { + log_error("Failed to allocate set."); + goto finish; + } + + while ((de = readdir(d))) { + char *path; + pid_t pid; + int k; + + if (!dirent_is_file(de)) + continue; + + if (asprintf(&path, "%s/%s", directory, de->d_name) < 0) { + log_oom(); + continue; + } + + if ((pid = fork()) < 0) { + log_error("Failed to fork: %m"); + free(path); + continue; + } + + if (pid == 0) { + char *_argv[2]; + /* Child */ + + if (!argv) { + _argv[0] = path; + _argv[1] = NULL; + argv = _argv; + } else + argv[0] = path; + + execv(path, argv); + + log_error("Failed to execute %s: %m", path); + _exit(EXIT_FAILURE); + } + + log_debug("Spawned %s as %lu", path, (unsigned long) pid); + + if ((k = hashmap_put(pids, UINT_TO_PTR(pid), path)) < 0) { + log_error("Failed to add PID to set: %s", strerror(-k)); + free(path); + } + } + + while (!hashmap_isempty(pids)) { + pid_t pid = PTR_TO_UINT(hashmap_first_key(pids)); + siginfo_t si = {}; + char *path; + + if (waitid(P_PID, pid, &si, WEXITED) < 0) { + + if (errno == EINTR) + continue; + + log_error("waitid() failed: %m"); + goto finish; + } + + if ((path = hashmap_remove(pids, UINT_TO_PTR(si.si_pid)))) { + if (!is_clean_exit(si.si_code, si.si_status, NULL)) { + if (si.si_code == CLD_EXITED) + log_error("%s exited with exit status %i.", path, si.si_status); + else + log_error("%s terminated by signal %s.", path, signal_to_string(si.si_status)); + } else + log_debug("%s exited successfully.", path); + + free(path); + } + } + +finish: + if (_d) + closedir(_d); + + if (pids) + hashmap_free_free(pids); +} + +int kill_and_sigcont(pid_t pid, int sig) { + int r; + + r = kill(pid, sig) < 0 ? -errno : 0; + + if (r >= 0) + kill(pid, SIGCONT); + + return r; +} + +bool nulstr_contains(const char*nulstr, const char *needle) { + const char *i; + + if (!nulstr) + return false; + + NULSTR_FOREACH(i, nulstr) + if (streq(i, needle)) + return true; + + return false; +} + +bool plymouth_running(void) { + return access("/run/plymouth/pid", F_OK) >= 0; +} + +char* strshorten(char *s, size_t l) { + assert(s); + + if (l < strlen(s)) + s[l] = 0; + + return s; +} + +static bool hostname_valid_char(char c) { + return + (c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9') || + c == '-' || + c == '_' || + c == '.'; +} + +bool hostname_is_valid(const char *s) { + const char *p; + bool dot; + + if (isempty(s)) + return false; + + for (p = s, dot = true; *p; p++) { + if (*p == '.') { + if (dot) + return false; + + dot = true; + } else { + if (!hostname_valid_char(*p)) + return false; + + dot = false; + } + } + + if (dot) + return false; + + if (p-s > HOST_NAME_MAX) + return false; + + return true; +} + +char* hostname_cleanup(char *s, bool lowercase) { + char *p, *d; + bool dot; + + for (p = s, d = s, dot = true; *p; p++) { + if (*p == '.') { + if (dot) + continue; + + *(d++) = '.'; + dot = true; + } else if (hostname_valid_char(*p)) { + *(d++) = lowercase ? tolower(*p) : *p; + dot = false; + } + + } + + if (dot && d > s) + d[-1] = 0; + else + *d = 0; + + strshorten(s, HOST_NAME_MAX); + + return s; +} + +int pipe_eof(int fd) { + int r; + struct pollfd pollfd = { + .fd = fd, + .events = POLLIN|POLLHUP, + }; + + r = poll(&pollfd, 1, 0); + if (r < 0) + return -errno; + + if (r == 0) + return 0; + + return pollfd.revents & POLLHUP; +} + +int fd_wait_for_event(int fd, int event, usec_t t) { + int r; + struct pollfd pollfd = { + .fd = fd, + .events = event, + }; + + r = poll(&pollfd, 1, t == (usec_t) -1 ? -1 : (int) (t / USEC_PER_MSEC)); + if (r < 0) + return -errno; + + if (r == 0) + return 0; + + return pollfd.revents; +} + +int fopen_temporary(const char *path, FILE **_f, char **_temp_path) { + FILE *f; + char *t; + const char *fn; + size_t k; + int fd; + + assert(path); + assert(_f); + assert(_temp_path); + + t = new(char, strlen(path) + 1 + 6 + 1); + if (!t) + return -ENOMEM; + + fn = path_get_file_name(path); + k = fn-path; + memcpy(t, path, k); + t[k] = '.'; + stpcpy(stpcpy(t+k+1, fn), "XXXXXX"); + + fd = mkostemp(t, O_WRONLY|O_CLOEXEC); + if (fd < 0) { + free(t); + return -errno; + } + + f = fdopen(fd, "we"); + if (!f) { + unlink(t); + free(t); + return -errno; + } + + *_f = f; + *_temp_path = t; + + return 0; +} + +int terminal_vhangup_fd(int fd) { + assert(fd >= 0); + + if (ioctl(fd, TIOCVHANGUP) < 0) + return -errno; + + return 0; +} + +int terminal_vhangup(const char *name) { + int fd, r; + + fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC); + if (fd < 0) + return fd; + + r = terminal_vhangup_fd(fd); + close_nointr_nofail(fd); + + return r; +} + +int vt_disallocate(const char *name) { + int fd, r; + unsigned u; + + /* Deallocate the VT if possible. If not possible + * (i.e. because it is the active one), at least clear it + * entirely (including the scrollback buffer) */ + + if (!startswith(name, "/dev/")) + return -EINVAL; + + if (!tty_is_vc(name)) { + /* So this is not a VT. I guess we cannot deallocate + * it then. But let's at least clear the screen */ + + fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC); + if (fd < 0) + return fd; + + loop_write(fd, + "\033[r" /* clear scrolling region */ + "\033[H" /* move home */ + "\033[2J", /* clear screen */ + 10, false); + close_nointr_nofail(fd); + + return 0; + } + + if (!startswith(name, "/dev/tty")) + return -EINVAL; + + r = safe_atou(name+8, &u); + if (r < 0) + return r; + + if (u <= 0) + return -EINVAL; + + /* Try to deallocate */ + fd = open_terminal("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC); + if (fd < 0) + return fd; + + r = ioctl(fd, VT_DISALLOCATE, u); + close_nointr_nofail(fd); + + if (r >= 0) + return 0; + + if (errno != EBUSY) + return -errno; + + /* Couldn't deallocate, so let's clear it fully with + * scrollback */ + fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC); + if (fd < 0) + return fd; + + loop_write(fd, + "\033[r" /* clear scrolling region */ + "\033[H" /* move home */ + "\033[3J", /* clear screen including scrollback, requires Linux 2.6.40 */ + 10, false); + close_nointr_nofail(fd); + + return 0; +} + +int copy_file(const char *from, const char *to) { + int r, fdf, fdt; + + assert(from); + assert(to); + + fdf = open(from, O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (fdf < 0) + return -errno; + + fdt = open(to, O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC|O_NOCTTY, 0644); + if (fdt < 0) { + close_nointr_nofail(fdf); + return -errno; + } + + for (;;) { + char buf[PIPE_BUF]; + ssize_t n, k; + + n = read(fdf, buf, sizeof(buf)); + if (n < 0) { + r = -errno; + + close_nointr_nofail(fdf); + close_nointr(fdt); + unlink(to); + + return r; + } + + if (n == 0) + break; + + errno = 0; + k = loop_write(fdt, buf, n, false); + if (n != k) { + r = k < 0 ? k : (errno ? -errno : -EIO); + + close_nointr_nofail(fdf); + close_nointr(fdt); + + unlink(to); + return r; + } + } + + close_nointr_nofail(fdf); + r = close_nointr(fdt); + + if (r < 0) { + unlink(to); + return r; + } + + return 0; +} + +int symlink_atomic(const char *from, const char *to) { + char *x; + _cleanup_free_ char *t; + const char *fn; + size_t k; + unsigned long long ull; + unsigned i; + int r; + + assert(from); + assert(to); + + t = new(char, strlen(to) + 1 + 16 + 1); + if (!t) + return -ENOMEM; + + fn = path_get_file_name(to); + k = fn-to; + memcpy(t, to, k); + t[k] = '.'; + x = stpcpy(t+k+1, fn); + + ull = random_ull(); + for (i = 0; i < 16; i++) { + *(x++) = hexchar(ull & 0xF); + ull >>= 4; + } + + *x = 0; + + if (symlink(from, t) < 0) + return -errno; + + if (rename(t, to) < 0) { + r = -errno; + unlink(t); + return r; + } + + return 0; +} + +bool display_is_local(const char *display) { + assert(display); + + return + display[0] == ':' && + display[1] >= '0' && + display[1] <= '9'; +} + +int socket_from_display(const char *display, char **path) { + size_t k; + char *f, *c; + + assert(display); + assert(path); + + if (!display_is_local(display)) + return -EINVAL; + + k = strspn(display+1, "0123456789"); + + f = new(char, sizeof("/tmp/.X11-unix/X") + k); + if (!f) + return -ENOMEM; + + c = stpcpy(f, "/tmp/.X11-unix/X"); + memcpy(c, display+1, k); + c[k] = 0; + + *path = f; + + return 0; +} + +int get_user_creds( + const char **username, + uid_t *uid, gid_t *gid, + const char **home, + const char **shell) { + + struct passwd *p; + uid_t u; + + assert(username); + assert(*username); + + /* We enforce some special rules for uid=0: in order to avoid + * NSS lookups for root we hardcode its data. */ + + if (streq(*username, "root") || streq(*username, "0")) { + *username = "root"; + + if (uid) + *uid = 0; + + if (gid) + *gid = 0; + + if (home) + *home = "/root"; + + if (shell) + *shell = "/bin/sh"; + + return 0; + } + + if (parse_uid(*username, &u) >= 0) { + errno = 0; + p = getpwuid(u); + + /* If there are multiple users with the same id, make + * sure to leave $USER to the configured value instead + * of the first occurrence in the database. However if + * the uid was configured by a numeric uid, then let's + * pick the real username from /etc/passwd. */ + if (p) + *username = p->pw_name; + } else { + errno = 0; + p = getpwnam(*username); + } + + if (!p) + return errno > 0 ? -errno : -ESRCH; + + if (uid) + *uid = p->pw_uid; + + if (gid) + *gid = p->pw_gid; + + if (home) + *home = p->pw_dir; + + if (shell) + *shell = p->pw_shell; + + return 0; +} + +char* uid_to_name(uid_t uid) { + struct passwd *p; + char *r; + + if (uid == 0) + return strdup("root"); + + p = getpwuid(uid); + if (p) + return strdup(p->pw_name); + + if (asprintf(&r, "%lu", (unsigned long) uid) < 0) + return NULL; + + return r; +} + +char* gid_to_name(gid_t gid) { + struct group *p; + char *r; + + if (gid == 0) + return strdup("root"); + + p = getgrgid(gid); + if (p) + return strdup(p->gr_name); + + if (asprintf(&r, "%lu", (unsigned long) gid) < 0) + return NULL; + + return r; +} + +int get_group_creds(const char **groupname, gid_t *gid) { + struct group *g; + gid_t id; + + assert(groupname); + + /* We enforce some special rules for gid=0: in order to avoid + * NSS lookups for root we hardcode its data. */ + + if (streq(*groupname, "root") || streq(*groupname, "0")) { + *groupname = "root"; + + if (gid) + *gid = 0; + + return 0; + } + + if (parse_gid(*groupname, &id) >= 0) { + errno = 0; + g = getgrgid(id); + + if (g) + *groupname = g->gr_name; + } else { + errno = 0; + g = getgrnam(*groupname); + } + + if (!g) + return errno > 0 ? -errno : -ESRCH; + + if (gid) + *gid = g->gr_gid; + + return 0; +} + +int in_gid(gid_t gid) { + gid_t *gids; + int ngroups_max, r, i; + + if (getgid() == gid) + return 1; + + if (getegid() == gid) + return 1; + + ngroups_max = sysconf(_SC_NGROUPS_MAX); + assert(ngroups_max > 0); + + gids = alloca(sizeof(gid_t) * ngroups_max); + + r = getgroups(ngroups_max, gids); + if (r < 0) + return -errno; + + for (i = 0; i < r; i++) + if (gids[i] == gid) + return 1; + + return 0; +} + +int in_group(const char *name) { + int r; + gid_t gid; + + r = get_group_creds(&name, &gid); + if (r < 0) + return r; + + return in_gid(gid); +} + +int glob_exists(const char *path) { + _cleanup_globfree_ glob_t g = {}; + int r, k; + + assert(path); + + errno = 0; + k = glob(path, GLOB_NOSORT|GLOB_BRACE, NULL, &g); + + if (k == GLOB_NOMATCH) + r = 0; + else if (k == GLOB_NOSPACE) + r = -ENOMEM; + else if (k == 0) + r = !strv_isempty(g.gl_pathv); + else + r = errno ? -errno : -EIO; + + return r; +} + +int dirent_ensure_type(DIR *d, struct dirent *de) { + struct stat st; + + assert(d); + assert(de); + + if (de->d_type != DT_UNKNOWN) + return 0; + + if (fstatat(dirfd(d), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) + return -errno; + + de->d_type = + S_ISREG(st.st_mode) ? DT_REG : + S_ISDIR(st.st_mode) ? DT_DIR : + S_ISLNK(st.st_mode) ? DT_LNK : + S_ISFIFO(st.st_mode) ? DT_FIFO : + S_ISSOCK(st.st_mode) ? DT_SOCK : + S_ISCHR(st.st_mode) ? DT_CHR : + S_ISBLK(st.st_mode) ? DT_BLK : + DT_UNKNOWN; + + return 0; +} + +int in_search_path(const char *path, char **search) { + char **i, *parent; + int r; + + r = path_get_parent(path, &parent); + if (r < 0) + return r; + + r = 0; + + STRV_FOREACH(i, search) { + if (path_equal(parent, *i)) { + r = 1; + break; + } + } + + free(parent); + + return r; +} + +int get_files_in_directory(const char *path, char ***list) { + DIR *d; + int r = 0; + unsigned n = 0; + char **l = NULL; + + assert(path); + + /* Returns all files in a directory in *list, and the number + * of files as return value. If list is NULL returns only the + * number */ + + d = opendir(path); + if (!d) + return -errno; + + for (;;) { + struct dirent *de; + union dirent_storage buf; + int k; + + k = readdir_r(d, &buf.de, &de); + if (k != 0) { + r = -k; + goto finish; + } + + if (!de) + break; + + dirent_ensure_type(d, de); + + if (!dirent_is_file(de)) + continue; + + if (list) { + if ((unsigned) r >= n) { + char **t; + + n = MAX(16, 2*r); + t = realloc(l, sizeof(char*) * n); + if (!t) { + r = -ENOMEM; + goto finish; + } + + l = t; + } + + assert((unsigned) r < n); + + l[r] = strdup(de->d_name); + if (!l[r]) { + r = -ENOMEM; + goto finish; + } + + l[++r] = NULL; + } else + r++; + } + +finish: + if (d) + closedir(d); + + if (r >= 0) { + if (list) + *list = l; + } else + strv_free(l); + + return r; +} + +char *strjoin(const char *x, ...) { + va_list ap; + size_t l; + char *r, *p; + + va_start(ap, x); + + if (x) { + l = strlen(x); + + for (;;) { + const char *t; + size_t n; + + t = va_arg(ap, const char *); + if (!t) + break; + + n = strlen(t); + if (n > ((size_t) -1) - l) { + va_end(ap); + return NULL; + } + + l += n; + } + } else + l = 0; + + va_end(ap); + + r = new(char, l+1); + if (!r) + return NULL; + + if (x) { + p = stpcpy(r, x); + + va_start(ap, x); + + for (;;) { + const char *t; + + t = va_arg(ap, const char *); + if (!t) + break; + + p = stpcpy(p, t); + } + + va_end(ap); + } else + r[0] = 0; + + return r; +} + +bool is_main_thread(void) { + static __thread int cached = 0; + + if (_unlikely_(cached == 0)) + cached = getpid() == gettid() ? 1 : -1; + + return cached > 0; +} + +int block_get_whole_disk(dev_t d, dev_t *ret) { + char *p, *s; + int r; + unsigned n, m; + + assert(ret); + + /* If it has a queue this is good enough for us */ + if (asprintf(&p, "/sys/dev/block/%u:%u/queue", major(d), minor(d)) < 0) + return -ENOMEM; + + r = access(p, F_OK); + free(p); + + if (r >= 0) { + *ret = d; + return 0; + } + + /* If it is a partition find the originating device */ + if (asprintf(&p, "/sys/dev/block/%u:%u/partition", major(d), minor(d)) < 0) + return -ENOMEM; + + r = access(p, F_OK); + free(p); + + if (r < 0) + return -ENOENT; + + /* Get parent dev_t */ + if (asprintf(&p, "/sys/dev/block/%u:%u/../dev", major(d), minor(d)) < 0) + return -ENOMEM; + + r = read_one_line_file(p, &s); + free(p); + + if (r < 0) + return r; + + r = sscanf(s, "%u:%u", &m, &n); + free(s); + + if (r != 2) + return -EINVAL; + + /* Only return this if it is really good enough for us. */ + if (asprintf(&p, "/sys/dev/block/%u:%u/queue", m, n) < 0) + return -ENOMEM; + + r = access(p, F_OK); + free(p); + + if (r >= 0) { + *ret = makedev(m, n); + return 0; + } + + return -ENOENT; +} + +int file_is_priv_sticky(const char *p) { + struct stat st; + + assert(p); + + if (lstat(p, &st) < 0) + return -errno; + + return + (st.st_uid == 0 || st.st_uid == getuid()) && + (st.st_mode & S_ISVTX); +} + +static const char *const ioprio_class_table[] = { + [IOPRIO_CLASS_NONE] = "none", + [IOPRIO_CLASS_RT] = "realtime", + [IOPRIO_CLASS_BE] = "best-effort", + [IOPRIO_CLASS_IDLE] = "idle" +}; + +DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(ioprio_class, int, INT_MAX); + +static const char *const sigchld_code_table[] = { + [CLD_EXITED] = "exited", + [CLD_KILLED] = "killed", + [CLD_DUMPED] = "dumped", + [CLD_TRAPPED] = "trapped", + [CLD_STOPPED] = "stopped", + [CLD_CONTINUED] = "continued", +}; + +DEFINE_STRING_TABLE_LOOKUP(sigchld_code, int); + +static const char *const log_facility_unshifted_table[LOG_NFACILITIES] = { + [LOG_FAC(LOG_KERN)] = "kern", + [LOG_FAC(LOG_USER)] = "user", + [LOG_FAC(LOG_MAIL)] = "mail", + [LOG_FAC(LOG_DAEMON)] = "daemon", + [LOG_FAC(LOG_AUTH)] = "auth", + [LOG_FAC(LOG_SYSLOG)] = "syslog", + [LOG_FAC(LOG_LPR)] = "lpr", + [LOG_FAC(LOG_NEWS)] = "news", + [LOG_FAC(LOG_UUCP)] = "uucp", + [LOG_FAC(LOG_CRON)] = "cron", + [LOG_FAC(LOG_AUTHPRIV)] = "authpriv", + [LOG_FAC(LOG_FTP)] = "ftp", + [LOG_FAC(LOG_LOCAL0)] = "local0", + [LOG_FAC(LOG_LOCAL1)] = "local1", + [LOG_FAC(LOG_LOCAL2)] = "local2", + [LOG_FAC(LOG_LOCAL3)] = "local3", + [LOG_FAC(LOG_LOCAL4)] = "local4", + [LOG_FAC(LOG_LOCAL5)] = "local5", + [LOG_FAC(LOG_LOCAL6)] = "local6", + [LOG_FAC(LOG_LOCAL7)] = "local7" +}; + +DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(log_facility_unshifted, int, LOG_FAC(~0)); + +static const char *const log_level_table[] = { + [LOG_EMERG] = "emerg", + [LOG_ALERT] = "alert", + [LOG_CRIT] = "crit", + [LOG_ERR] = "err", + [LOG_WARNING] = "warning", + [LOG_NOTICE] = "notice", + [LOG_INFO] = "info", + [LOG_DEBUG] = "debug" +}; + +DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(log_level, int, LOG_DEBUG); + +static const char* const sched_policy_table[] = { + [SCHED_OTHER] = "other", + [SCHED_BATCH] = "batch", + [SCHED_IDLE] = "idle", + [SCHED_FIFO] = "fifo", + [SCHED_RR] = "rr" +}; + +DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(sched_policy, int, INT_MAX); + +static const char* const rlimit_table[] = { + [RLIMIT_CPU] = "LimitCPU", + [RLIMIT_FSIZE] = "LimitFSIZE", + [RLIMIT_DATA] = "LimitDATA", + [RLIMIT_STACK] = "LimitSTACK", + [RLIMIT_CORE] = "LimitCORE", + [RLIMIT_RSS] = "LimitRSS", + [RLIMIT_NOFILE] = "LimitNOFILE", + [RLIMIT_AS] = "LimitAS", + [RLIMIT_NPROC] = "LimitNPROC", + [RLIMIT_MEMLOCK] = "LimitMEMLOCK", + [RLIMIT_LOCKS] = "LimitLOCKS", + [RLIMIT_SIGPENDING] = "LimitSIGPENDING", + [RLIMIT_MSGQUEUE] = "LimitMSGQUEUE", + [RLIMIT_NICE] = "LimitNICE", + [RLIMIT_RTPRIO] = "LimitRTPRIO", + [RLIMIT_RTTIME] = "LimitRTTIME" +}; + +DEFINE_STRING_TABLE_LOOKUP(rlimit, int); + +static const char* const ip_tos_table[] = { + [IPTOS_LOWDELAY] = "low-delay", + [IPTOS_THROUGHPUT] = "throughput", + [IPTOS_RELIABILITY] = "reliability", + [IPTOS_LOWCOST] = "low-cost", +}; + +DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(ip_tos, int, 0xff); + +static const char *const __signal_table[] = { + [SIGHUP] = "HUP", + [SIGINT] = "INT", + [SIGQUIT] = "QUIT", + [SIGILL] = "ILL", + [SIGTRAP] = "TRAP", + [SIGABRT] = "ABRT", + [SIGBUS] = "BUS", + [SIGFPE] = "FPE", + [SIGKILL] = "KILL", + [SIGUSR1] = "USR1", + [SIGSEGV] = "SEGV", + [SIGUSR2] = "USR2", + [SIGPIPE] = "PIPE", + [SIGALRM] = "ALRM", + [SIGTERM] = "TERM", +#ifdef SIGSTKFLT + [SIGSTKFLT] = "STKFLT", /* Linux on SPARC doesn't know SIGSTKFLT */ +#endif + [SIGCHLD] = "CHLD", + [SIGCONT] = "CONT", + [SIGSTOP] = "STOP", + [SIGTSTP] = "TSTP", + [SIGTTIN] = "TTIN", + [SIGTTOU] = "TTOU", + [SIGURG] = "URG", + [SIGXCPU] = "XCPU", + [SIGXFSZ] = "XFSZ", + [SIGVTALRM] = "VTALRM", + [SIGPROF] = "PROF", + [SIGWINCH] = "WINCH", + [SIGIO] = "IO", + [SIGPWR] = "PWR", + [SIGSYS] = "SYS" +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP(__signal, int); + +const char *signal_to_string(int signo) { + static __thread char buf[sizeof("RTMIN+")-1 + DECIMAL_STR_MAX(int) + 1]; + const char *name; + + name = __signal_to_string(signo); + if (name) + return name; + + if (signo >= SIGRTMIN && signo <= SIGRTMAX) + snprintf(buf, sizeof(buf), "RTMIN+%d", signo - SIGRTMIN); + else + snprintf(buf, sizeof(buf), "%d", signo); + + return buf; +} + +int signal_from_string(const char *s) { + int signo; + int offset = 0; + unsigned u; + + signo = __signal_from_string(s); + if (signo > 0) + return signo; + + if (startswith(s, "RTMIN+")) { + s += 6; + offset = SIGRTMIN; + } + if (safe_atou(s, &u) >= 0) { + signo = (int) u + offset; + if (signo > 0 && signo < _NSIG) + return signo; + } + return -1; +} + +bool kexec_loaded(void) { + bool loaded = false; + char *s; + + if (read_one_line_file("/sys/kernel/kexec_loaded", &s) >= 0) { + if (s[0] == '1') + loaded = true; + free(s); + } + return loaded; +} + +int strdup_or_null(const char *a, char **b) { + char *c; + + assert(b); + + if (!a) { + *b = NULL; + return 0; + } + + c = strdup(a); + if (!c) + return -ENOMEM; + + *b = c; + return 0; +} + +int prot_from_flags(int flags) { + + switch (flags & O_ACCMODE) { + + case O_RDONLY: + return PROT_READ; + + case O_WRONLY: + return PROT_WRITE; + + case O_RDWR: + return PROT_READ|PROT_WRITE; + + default: + return -EINVAL; + } +} + +char *format_bytes(char *buf, size_t l, off_t t) { + unsigned i; + + static const struct { + const char *suffix; + off_t factor; + } table[] = { + { "E", 1024ULL*1024ULL*1024ULL*1024ULL*1024ULL*1024ULL }, + { "P", 1024ULL*1024ULL*1024ULL*1024ULL*1024ULL }, + { "T", 1024ULL*1024ULL*1024ULL*1024ULL }, + { "G", 1024ULL*1024ULL*1024ULL }, + { "M", 1024ULL*1024ULL }, + { "K", 1024ULL }, + }; + + for (i = 0; i < ELEMENTSOF(table); i++) { + + if (t >= table[i].factor) { + snprintf(buf, l, + "%llu.%llu%s", + (unsigned long long) (t / table[i].factor), + (unsigned long long) (((t*10ULL) / table[i].factor) % 10ULL), + table[i].suffix); + + goto finish; + } + } + + snprintf(buf, l, "%lluB", (unsigned long long) t); + +finish: + buf[l-1] = 0; + return buf; + +} + +void* memdup(const void *p, size_t l) { + void *r; + + assert(p); + + r = malloc(l); + if (!r) + return NULL; + + memcpy(r, p, l); + return r; +} + +int fd_inc_sndbuf(int fd, size_t n) { + int r, value; + socklen_t l = sizeof(value); + + r = getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &value, &l); + if (r >= 0 && + l == sizeof(value) && + (size_t) value >= n*2) + return 0; + + value = (int) n; + r = setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &value, sizeof(value)); + if (r < 0) + return -errno; + + return 1; +} + +int fd_inc_rcvbuf(int fd, size_t n) { + int r, value; + socklen_t l = sizeof(value); + + r = getsockopt(fd, SOL_SOCKET, SO_RCVBUF, &value, &l); + if (r >= 0 && + l == sizeof(value) && + (size_t) value >= n*2) + return 0; + + value = (int) n; + r = setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &value, sizeof(value)); + if (r < 0) + return -errno; + + return 1; +} + +int fork_agent(pid_t *pid, const int except[], unsigned n_except, const char *path, ...) { + pid_t parent_pid, agent_pid; + int fd; + bool stdout_is_tty, stderr_is_tty; + unsigned n, i; + va_list ap; + char **l; + + assert(pid); + assert(path); + + parent_pid = getpid(); + + /* Spawns a temporary TTY agent, making sure it goes away when + * we go away */ + + agent_pid = fork(); + if (agent_pid < 0) + return -errno; + + if (agent_pid != 0) { + *pid = agent_pid; + return 0; + } + + /* In the child: + * + * Make sure the agent goes away when the parent dies */ + if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0) + _exit(EXIT_FAILURE); + + /* Check whether our parent died before we were able + * to set the death signal */ + if (getppid() != parent_pid) + _exit(EXIT_SUCCESS); + + /* Don't leak fds to the agent */ + close_all_fds(except, n_except); + + stdout_is_tty = isatty(STDOUT_FILENO); + stderr_is_tty = isatty(STDERR_FILENO); + + if (!stdout_is_tty || !stderr_is_tty) { + /* 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("Failed to open /dev/tty: %m"); + _exit(EXIT_FAILURE); + } + + if (!stdout_is_tty) + dup2(fd, STDOUT_FILENO); + + if (!stderr_is_tty) + dup2(fd, STDERR_FILENO); + + if (fd > 2) + close(fd); + } + + /* Count arguments */ + va_start(ap, path); + for (n = 0; va_arg(ap, char*); n++) + ; + va_end(ap); + + /* Allocate strv */ + l = alloca(sizeof(char *) * (n + 1)); + + /* Fill in arguments */ + va_start(ap, path); + for (i = 0; i <= n; i++) + l[i] = va_arg(ap, char*); + va_end(ap); + + execv(path, l); + _exit(EXIT_FAILURE); +} + +int setrlimit_closest(int resource, const struct rlimit *rlim) { + struct rlimit highest, fixed; + + assert(rlim); + + if (setrlimit(resource, rlim) >= 0) + return 0; + + if (errno != EPERM) + return -errno; + + /* So we failed to set the desired setrlimit, then let's try + * to get as close as we can */ + assert_se(getrlimit(resource, &highest) == 0); + + fixed.rlim_cur = MIN(rlim->rlim_cur, highest.rlim_max); + fixed.rlim_max = MIN(rlim->rlim_max, highest.rlim_max); + + if (setrlimit(resource, &fixed) < 0) + return -errno; + + return 0; +} + +int getenv_for_pid(pid_t pid, const char *field, char **_value) { + _cleanup_fclose_ FILE *f = NULL; + char *value = NULL; + int r; + bool done = false; + size_t l; + const char *path; + + assert(pid >= 0); + assert(field); + assert(_value); + + if (pid == 0) + path = "/proc/self/environ"; + else + path = procfs_file_alloca(pid, "environ"); + + f = fopen(path, "re"); + if (!f) + return -errno; + + l = strlen(field); + r = 0; + + do { + char line[LINE_MAX]; + unsigned i; + + for (i = 0; i < sizeof(line)-1; i++) { + int c; + + c = getc(f); + if (_unlikely_(c == EOF)) { + done = true; + break; + } else if (c == 0) + break; + + line[i] = c; + } + line[i] = 0; + + if (memcmp(line, field, l) == 0 && line[l] == '=') { + value = strdup(line + l + 1); + if (!value) + return -ENOMEM; + + r = 1; + break; + } + + } while (!done); + + *_value = value; + return r; +} + +bool is_valid_documentation_url(const char *url) { + assert(url); + + if (startswith(url, "http://") && url[7]) + return true; + + if (startswith(url, "https://") && url[8]) + return true; + + if (startswith(url, "file:") && url[5]) + return true; + + if (startswith(url, "info:") && url[5]) + return true; + + if (startswith(url, "man:") && url[4]) + return true; + + return false; +} + +bool in_initrd(void) { + static __thread int saved = -1; + struct statfs s; + + if (saved >= 0) + return saved; + + /* We make two checks here: + * + * 1. the flag file /etc/initrd-release must exist + * 2. the root file system must be a memory file system + * + * The second check is extra paranoia, since misdetecting an + * initrd can have bad bad consequences due the initrd + * emptying when transititioning to the main systemd. + */ + + saved = access("/etc/initrd-release", F_OK) >= 0 && + statfs("/", &s) >= 0 && + is_temporary_fs(&s); + + return saved; +} + +void warn_melody(void) { + _cleanup_close_ int fd = -1; + + fd = open("/dev/console", O_WRONLY|O_CLOEXEC|O_NOCTTY); + if (fd < 0) + return; + + /* Yeah, this is synchronous. Kinda sucks. But well... */ + + ioctl(fd, KIOCSOUND, (int)(1193180/440)); + usleep(125*USEC_PER_MSEC); + + ioctl(fd, KIOCSOUND, (int)(1193180/220)); + usleep(125*USEC_PER_MSEC); + + ioctl(fd, KIOCSOUND, (int)(1193180/220)); + usleep(125*USEC_PER_MSEC); + + ioctl(fd, KIOCSOUND, 0); +} + +int make_console_stdio(void) { + int fd, r; + + /* Make /dev/console the controlling terminal and stdin/stdout/stderr */ + + fd = acquire_terminal("/dev/console", false, true, true, (usec_t) -1); + if (fd < 0) { + log_error("Failed to acquire terminal: %s", strerror(-fd)); + return fd; + } + + r = make_stdio(fd); + if (r < 0) { + log_error("Failed to duplicate terminal fd: %s", strerror(-r)); + return r; + } + + return 0; +} + +int get_home_dir(char **_h) { + char *h; + const char *e; + uid_t u; + struct passwd *p; + + assert(_h); + + /* Take the user specified one */ + e = getenv("HOME"); + if (e) { + h = strdup(e); + if (!h) + return -ENOMEM; + + *_h = h; + return 0; + } + + /* Hardcode home directory for root to avoid NSS */ + u = getuid(); + if (u == 0) { + h = strdup("/root"); + if (!h) + return -ENOMEM; + + *_h = h; + return 0; + } + + /* Check the database... */ + errno = 0; + p = getpwuid(u); + if (!p) + return errno > 0 ? -errno : -ESRCH; + + if (!path_is_absolute(p->pw_dir)) + return -EINVAL; + + h = strdup(p->pw_dir); + if (!h) + return -ENOMEM; + + *_h = h; + return 0; +} + +bool filename_is_safe(const char *p) { + + if (isempty(p)) + return false; + + if (strchr(p, '/')) + return false; + + if (streq(p, ".")) + return false; + + if (streq(p, "..")) + return false; + + if (strlen(p) > FILENAME_MAX) + return false; + + return true; +} + +bool string_is_safe(const char *p) { + const char *t; + + assert(p); + + for (t = p; *t; t++) { + if (*t > 0 && *t < ' ') + return false; + + if (strchr("\\\"\'", *t)) + return false; + } + + return true; +} + +bool string_has_cc(const char *p) { + const char *t; + + assert(p); + + for (t = p; *t; t++) + if (*t > 0 && *t < ' ') + return true; + + return false; +} + +bool path_is_safe(const char *p) { + + if (isempty(p)) + return false; + + if (streq(p, "..") || startswith(p, "../") || endswith(p, "/..") || strstr(p, "/../")) + return false; + + if (strlen(p) > PATH_MAX) + return false; + + /* The following two checks are not really dangerous, but hey, they still are confusing */ + if (streq(p, ".") || startswith(p, "./") || endswith(p, "/.") || strstr(p, "/./")) + return false; + + if (strstr(p, "//")) + return false; + + return true; +} + +/* hey glibc, APIs with callbacks without a user pointer are so useless */ +void *xbsearch_r(const void *key, const void *base, size_t nmemb, size_t size, + int (*compar) (const void *, const void *, void *), void *arg) { + size_t l, u, idx; + const void *p; + int comparison; + + l = 0; + u = nmemb; + while (l < u) { + idx = (l + u) / 2; + p = (void *)(((const char *) base) + (idx * size)); + comparison = compar(key, p, arg); + if (comparison < 0) + u = idx; + else if (comparison > 0) + l = idx + 1; + else + return (void *)p; + } + return NULL; +} + +bool is_locale_utf8(void) { + const char *set; + static int cached_answer = -1; + + if (cached_answer >= 0) + goto out; + + if (!setlocale(LC_ALL, "")) { + cached_answer = true; + goto out; + } + + set = nl_langinfo(CODESET); + if (!set) { + cached_answer = true; + goto out; + } + + if(streq(set, "UTF-8")) { + cached_answer = true; + goto out; + } + + /* For LC_CTYPE=="C" return true, + * because CTYPE is effectly unset and + * everything defaults to UTF-8 nowadays. */ + + set = setlocale(LC_CTYPE, NULL); + if (!set) { + cached_answer = true; + goto out; + } + + cached_answer = streq(set, "C"); + +out: + return (bool)cached_answer; +} + +const char *draw_special_char(DrawSpecialChar ch) { + static const char *draw_table[2][_DRAW_SPECIAL_CHAR_MAX] = { + /* UTF-8 */ { + [DRAW_TREE_VERT] = "\342\224\202 ", /* │ */ + [DRAW_TREE_BRANCH] = "\342\224\234\342\224\200", /* ├─ */ + [DRAW_TREE_RIGHT] = "\342\224\224\342\224\200", /* └─ */ + [DRAW_TREE_SPACE] = " ", /* */ + [DRAW_TRIANGULAR_BULLET] = "\342\200\243 ", /* ‣ */ + }, + /* ASCII fallback */ { + [DRAW_TREE_VERT] = "| ", + [DRAW_TREE_BRANCH] = "|-", + [DRAW_TREE_RIGHT] = "`-", + [DRAW_TREE_SPACE] = " ", + [DRAW_TRIANGULAR_BULLET] = "> ", + } + }; + + return draw_table[!is_locale_utf8()][ch]; +} + +char *strreplace(const char *text, const char *old_string, const char *new_string) { + const char *f; + char *t, *r; + size_t l, old_len, new_len; + + assert(text); + assert(old_string); + assert(new_string); + + old_len = strlen(old_string); + new_len = strlen(new_string); + + l = strlen(text); + r = new(char, l+1); + if (!r) + return NULL; + + f = text; + t = r; + while (*f) { + char *a; + size_t d, nl; + + if (!startswith(f, old_string)) { + *(t++) = *(f++); + continue; + } + + d = t - r; + nl = l - old_len + new_len; + a = realloc(r, nl + 1); + if (!a) + goto oom; + + l = nl; + r = a; + t = r + d; + + t = stpcpy(t, new_string); + f += old_len; + } + + *t = 0; + return r; + +oom: + free(r); + return NULL; +} + +char *strip_tab_ansi(char **ibuf, size_t *_isz) { + const char *i, *begin = NULL; + enum { + STATE_OTHER, + STATE_ESCAPE, + STATE_BRACKET + } state = STATE_OTHER; + char *obuf = NULL; + size_t osz = 0, isz; + FILE *f; + + assert(ibuf); + assert(*ibuf); + + /* Strips ANSI color and replaces TABs by 8 spaces */ + + isz = _isz ? *_isz : strlen(*ibuf); + + f = open_memstream(&obuf, &osz); + if (!f) + return NULL; + + for (i = *ibuf; i < *ibuf + isz + 1; i++) { + + switch (state) { + + case STATE_OTHER: + if (i >= *ibuf + isz) /* EOT */ + break; + else if (*i == '\x1B') + state = STATE_ESCAPE; + else if (*i == '\t') + fputs(" ", f); + else + fputc(*i, f); + break; + + case STATE_ESCAPE: + if (i >= *ibuf + isz) { /* EOT */ + fputc('\x1B', f); + break; + } else if (*i == '[') { + state = STATE_BRACKET; + begin = i + 1; + } else { + fputc('\x1B', f); + fputc(*i, f); + state = STATE_OTHER; + } + + break; + + case STATE_BRACKET: + + if (i >= *ibuf + isz || /* EOT */ + (!(*i >= '0' && *i <= '9') && *i != ';' && *i != 'm')) { + fputc('\x1B', f); + fputc('[', f); + state = STATE_OTHER; + i = begin-1; + } else if (*i == 'm') + state = STATE_OTHER; + break; + } + } + + if (ferror(f)) { + fclose(f); + free(obuf); + return NULL; + } + + fclose(f); + + free(*ibuf); + *ibuf = obuf; + + if (_isz) + *_isz = osz; + + return obuf; +} + +int on_ac_power(void) { + bool found_offline = false, found_online = false; + _cleanup_closedir_ DIR *d = NULL; + + d = opendir("/sys/class/power_supply"); + if (!d) + return -errno; + + for (;;) { + struct dirent *de; + union dirent_storage buf; + _cleanup_close_ int fd = -1, device = -1; + char contents[6]; + ssize_t n; + int k; + + k = readdir_r(d, &buf.de, &de); + if (k != 0) + return -k; + + if (!de) + break; + + if (ignore_file(de->d_name)) + continue; + + device = openat(dirfd(d), de->d_name, O_DIRECTORY|O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (device < 0) { + if (errno == ENOENT || errno == ENOTDIR) + continue; + + return -errno; + } + + fd = openat(device, "type", O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (fd < 0) { + if (errno == ENOENT) + continue; + + return -errno; + } + + n = read(fd, contents, sizeof(contents)); + if (n < 0) + return -errno; + + if (n != 6 || memcmp(contents, "Mains\n", 6)) + continue; + + close_nointr_nofail(fd); + fd = openat(device, "online", O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (fd < 0) { + if (errno == ENOENT) + continue; + + return -errno; + } + + n = read(fd, contents, sizeof(contents)); + if (n < 0) + return -errno; + + if (n != 2 || contents[1] != '\n') + return -EIO; + + if (contents[0] == '1') { + found_online = true; + break; + } else if (contents[0] == '0') + found_offline = true; + else + return -EIO; + } + + return found_online || !found_offline; +} + +static int search_and_fopen_internal(const char *path, const char *mode, char **search, FILE **_f) { + char **i; + + assert(path); + assert(mode); + assert(_f); + + if (!path_strv_canonicalize_uniq(search)) + return -ENOMEM; + + STRV_FOREACH(i, search) { + _cleanup_free_ char *p = NULL; + FILE *f; + + p = strjoin(*i, "/", path, NULL); + if (!p) + return -ENOMEM; + + f = fopen(p, mode); + if (f) { + *_f = f; + return 0; + } + + if (errno != ENOENT) + return -errno; + } + + return -ENOENT; +} + +int search_and_fopen(const char *path, const char *mode, const char **search, FILE **_f) { + _cleanup_strv_free_ char **copy = NULL; + + assert(path); + assert(mode); + assert(_f); + + if (path_is_absolute(path)) { + FILE *f; + + f = fopen(path, mode); + if (f) { + *_f = f; + return 0; + } + + return -errno; + } + + copy = strv_copy((char**) search); + if (!copy) + return -ENOMEM; + + return search_and_fopen_internal(path, mode, copy, _f); +} + +int search_and_fopen_nulstr(const char *path, const char *mode, const char *search, FILE **_f) { + _cleanup_strv_free_ char **s = NULL; + + if (path_is_absolute(path)) { + FILE *f; + + f = fopen(path, mode); + if (f) { + *_f = f; + return 0; + } + + return -errno; + } + + s = strv_split_nulstr(search); + if (!s) + return -ENOMEM; + + return search_and_fopen_internal(path, mode, s, _f); +} + +int create_tmp_dir(char template[], char** dir_name) { + int r = 0; + char *d, *dt; + + assert(dir_name); + + RUN_WITH_UMASK(0077) { + d = mkdtemp(template); + } + if (!d) { + log_error("Can't create directory %s: %m", template); + return -errno; + } + + dt = strjoin(d, "/tmp", NULL); + if (!dt) { + r = log_oom(); + goto fail3; + } + + RUN_WITH_UMASK(0000) { + r = mkdir(dt, 0777); + } + if (r < 0) { + log_error("Can't create directory %s: %m", dt); + r = -errno; + goto fail2; + } + log_debug("Created temporary directory %s", dt); + + r = chmod(dt, 0777 | S_ISVTX); + if (r < 0) { + log_error("Failed to chmod %s: %m", dt); + r = -errno; + goto fail1; + } + log_debug("Set sticky bit on %s", dt); + + *dir_name = dt; + + return 0; +fail1: + rmdir(dt); +fail2: + free(dt); +fail3: + rmdir(template); + return r; +} + +char *strextend(char **x, ...) { + va_list ap; + size_t f, l; + char *r, *p; + + assert(x); + + l = f = *x ? strlen(*x) : 0; + + va_start(ap, x); + for (;;) { + const char *t; + size_t n; + + t = va_arg(ap, const char *); + if (!t) + break; + + n = strlen(t); + if (n > ((size_t) -1) - l) { + va_end(ap); + return NULL; + } + + l += n; + } + va_end(ap); + + r = realloc(*x, l+1); + if (!r) + return NULL; + + p = r + f; + + va_start(ap, x); + for (;;) { + const char *t; + + t = va_arg(ap, const char *); + if (!t) + break; + + p = stpcpy(p, t); + } + va_end(ap); + + *p = 0; + *x = r; + + return r + l; +} + +char *strrep(const char *s, unsigned n) { + size_t l; + char *r, *p; + unsigned i; + + assert(s); + + l = strlen(s); + p = r = malloc(l * n + 1); + if (!r) + return NULL; + + for (i = 0; i < n; i++) + p = stpcpy(p, s); + + *p = 0; + return r; +} + +void* greedy_realloc(void **p, size_t *allocated, size_t need) { + size_t a; + void *q; + + if (*allocated >= need) + return *p; + + a = MAX(64u, need * 2); + q = realloc(*p, a); + if (!q) + return NULL; + + *p = q; + *allocated = a; + return q; +} + +bool id128_is_valid(const char *s) { + size_t i, l; + + l = strlen(s); + if (l == 32) { + + /* Simple formatted 128bit hex string */ + + for (i = 0; i < l; i++) { + char c = s[i]; + + if (!(c >= '0' && c <= '9') && + !(c >= 'a' && c <= 'z') && + !(c >= 'A' && c <= 'Z')) + return false; + } + + } else if (l == 36) { + + /* Formatted UUID */ + + for (i = 0; i < l; i++) { + char c = s[i]; + + if ((i == 8 || i == 13 || i == 18 || i == 23)) { + if (c != '-') + return false; + } else { + if (!(c >= '0' && c <= '9') && + !(c >= 'a' && c <= 'z') && + !(c >= 'A' && c <= 'Z')) + return false; + } + } + + } else + return false; + + return true; +} diff --git a/src/shared/util.h b/src/shared/util.h new file mode 100644 index 0000000000..64e63b8c07 --- /dev/null +++ b/src/shared/util.h @@ -0,0 +1,734 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <alloca.h> +#include <inttypes.h> +#include <time.h> +#include <sys/time.h> +#include <stdarg.h> +#include <stdbool.h> +#include <stdlib.h> +#include <stdio.h> +#include <signal.h> +#include <sched.h> +#include <limits.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <dirent.h> +#include <sys/resource.h> +#include <stddef.h> +#include <unistd.h> +#include <locale.h> + +#include "macro.h" +#include "time-util.h" + +union dirent_storage { + struct dirent de; + uint8_t storage[offsetof(struct dirent, d_name) + + ((NAME_MAX + 1 + sizeof(long)) & ~(sizeof(long) - 1))]; +}; + +/* What is interpreted as whitespace? */ +#define WHITESPACE " \t\n\r" +#define NEWLINE "\n\r" +#define QUOTES "\"\'" +#define COMMENTS "#;" + +#define FORMAT_BYTES_MAX 8 + +#define ANSI_HIGHLIGHT_ON "\x1B[1;39m" +#define ANSI_RED_ON "\x1B[31m" +#define ANSI_HIGHLIGHT_RED_ON "\x1B[1;31m" +#define ANSI_GREEN_ON "\x1B[32m" +#define ANSI_HIGHLIGHT_GREEN_ON "\x1B[1;32m" +#define ANSI_HIGHLIGHT_YELLOW_ON "\x1B[1;33m" +#define ANSI_HIGHLIGHT_OFF "\x1B[0m" +#define ANSI_ERASE_TO_END_OF_LINE "\x1B[K" + +size_t page_size(void); +#define PAGE_ALIGN(l) ALIGN_TO((l), page_size()) + +#define streq(a,b) (strcmp((a),(b)) == 0) +#define strneq(a, b, n) (strncmp((a), (b), (n)) == 0) +#define strcaseeq(a,b) (strcasecmp((a),(b)) == 0) +#define strncaseeq(a, b, n) (strncasecmp((a), (b), (n)) == 0) + +bool streq_ptr(const char *a, const char *b) _pure_; + +#define new(t, n) ((t*) malloc_multiply(sizeof(t), (n))) + +#define new0(t, n) ((t*) calloc((n), sizeof(t))) + +#define newa(t, n) ((t*) alloca(sizeof(t)*(n))) + +#define newdup(t, p, n) ((t*) memdup_multiply(p, sizeof(t), (n))) + +#define malloc0(n) (calloc((n), 1)) + +static inline const char* yes_no(bool b) { + return b ? "yes" : "no"; +} + +static inline const char* strempty(const char *s) { + return s ? s : ""; +} + +static inline const char* strnull(const char *s) { + return s ? s : "(null)"; +} + +static inline const char *strna(const char *s) { + return s ? s : "n/a"; +} + +static inline bool isempty(const char *p) { + return !p || !p[0]; +} + +char *endswith(const char *s, const char *postfix) _pure_; +char *startswith(const char *s, const char *prefix) _pure_; +char *startswith_no_case(const char *s, const char *prefix) _pure_; + +bool first_word(const char *s, const char *word) _pure_; + +int close_nointr(int fd); +void close_nointr_nofail(int fd); +void close_many(const int fds[], unsigned n_fd); + +int parse_boolean(const char *v) _pure_; +int parse_bytes(const char *t, off_t *bytes); +int parse_pid(const char *s, pid_t* ret_pid); +int parse_uid(const char *s, uid_t* ret_uid); +#define parse_gid(s, ret_uid) parse_uid(s, ret_uid) + +int safe_atou(const char *s, unsigned *ret_u); +int safe_atoi(const char *s, int *ret_i); + +int safe_atollu(const char *s, unsigned long long *ret_u); +int safe_atolli(const char *s, long long int *ret_i); + +int safe_atod(const char *s, double *ret_d); + +#if __WORDSIZE == 32 +static inline int safe_atolu(const char *s, unsigned long *ret_u) { + assert_cc(sizeof(unsigned long) == sizeof(unsigned)); + return safe_atou(s, (unsigned*) ret_u); +} +static inline int safe_atoli(const char *s, long int *ret_u) { + assert_cc(sizeof(long int) == sizeof(int)); + return safe_atoi(s, (int*) ret_u); +} +#else +static inline int safe_atolu(const char *s, unsigned long *ret_u) { + assert_cc(sizeof(unsigned long) == sizeof(unsigned long long)); + return safe_atollu(s, (unsigned long long*) ret_u); +} +static inline int safe_atoli(const char *s, long int *ret_u) { + assert_cc(sizeof(long int) == sizeof(long long int)); + return safe_atolli(s, (long long int*) ret_u); +} +#endif + +static inline int safe_atou32(const char *s, uint32_t *ret_u) { + assert_cc(sizeof(uint32_t) == sizeof(unsigned)); + return safe_atou(s, (unsigned*) ret_u); +} + +static inline int safe_atoi32(const char *s, int32_t *ret_i) { + assert_cc(sizeof(int32_t) == sizeof(int)); + return safe_atoi(s, (int*) ret_i); +} + +static inline int safe_atou64(const char *s, uint64_t *ret_u) { + assert_cc(sizeof(uint64_t) == sizeof(unsigned long long)); + return safe_atollu(s, (unsigned long long*) ret_u); +} + +static inline int safe_atoi64(const char *s, int64_t *ret_i) { + assert_cc(sizeof(int64_t) == sizeof(long long int)); + return safe_atolli(s, (long long int*) ret_i); +} + +char *split(const char *c, size_t *l, const char *separator, char **state); +char *split_quoted(const char *c, size_t *l, char **state); + +#define FOREACH_WORD(word, length, s, state) \ + for ((state) = NULL, (word) = split((s), &(length), WHITESPACE, &(state)); (word); (word) = split((s), &(length), WHITESPACE, &(state))) + +#define FOREACH_WORD_SEPARATOR(word, length, s, separator, state) \ + for ((state) = NULL, (word) = split((s), &(length), (separator), &(state)); (word); (word) = split((s), &(length), (separator), &(state))) + +#define FOREACH_WORD_QUOTED(word, length, s, state) \ + for ((state) = NULL, (word) = split_quoted((s), &(length), &(state)); (word); (word) = split_quoted((s), &(length), &(state))) + +pid_t get_parent_of_pid(pid_t pid, pid_t *ppid); +int get_starttime_of_pid(pid_t pid, unsigned long long *st); + +char *strappend(const char *s, const char *suffix); +char *strnappend(const char *s, const char *suffix, size_t length); + +char *replace_env(const char *format, char **env); +char **replace_env_argv(char **argv, char **env); + +int readlink_malloc(const char *p, char **r); +int readlink_and_make_absolute(const char *p, char **r); +int readlink_and_canonicalize(const char *p, char **r); + +int reset_all_signal_handlers(void); + +char *strstrip(char *s); +char *delete_chars(char *s, const char *bad); +char *truncate_nl(char *s); + +char *file_in_same_dir(const char *path, const char *filename); + +int rmdir_parents(const char *path, const char *stop); + +int get_process_comm(pid_t pid, char **name); +int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char **line); +int get_process_exe(pid_t pid, char **name); +int get_process_uid(pid_t pid, uid_t *uid); +int get_process_gid(pid_t pid, gid_t *gid); + +char hexchar(int x) _const_; +int unhexchar(char c) _const_; +char octchar(int x) _const_; +int unoctchar(char c) _const_; +char decchar(int x) _const_; +int undecchar(char c) _const_; + +char *cescape(const char *s); +char *cunescape(const char *s); +char *cunescape_length(const char *s, size_t length); +char *cunescape_length_with_prefix(const char *s, size_t length, const char *prefix); + +char *xescape(const char *s, const char *bad); + +char *bus_path_escape(const char *s); +char *bus_path_unescape(const char *s); + +char *ascii_strlower(char *path); + +bool dirent_is_file(const struct dirent *de) _pure_; +bool dirent_is_file_with_suffix(const struct dirent *de, const char *suffix) _pure_; + +bool ignore_file(const char *filename) _pure_; + +bool chars_intersect(const char *a, const char *b) _pure_; + +int make_stdio(int fd); +int make_null_stdio(void); +int make_console_stdio(void); + +unsigned long long random_ull(void); + +/* For basic lookup tables with strictly enumerated entries */ +#define __DEFINE_STRING_TABLE_LOOKUP(name,type,scope) \ + scope const char *name##_to_string(type i) { \ + if (i < 0 || i >= (type) ELEMENTSOF(name##_table)) \ + return NULL; \ + return name##_table[i]; \ + } \ + scope type name##_from_string(const char *s) { \ + type i; \ + assert(s); \ + for (i = 0; i < (type)ELEMENTSOF(name##_table); i++) \ + if (name##_table[i] && \ + streq(name##_table[i], s)) \ + return i; \ + return (type) -1; \ + } \ + struct __useless_struct_to_allow_trailing_semicolon__ + +#define DEFINE_STRING_TABLE_LOOKUP(name,type) __DEFINE_STRING_TABLE_LOOKUP(name,type,) +#define DEFINE_PRIVATE_STRING_TABLE_LOOKUP(name,type) __DEFINE_STRING_TABLE_LOOKUP(name,type,static) + +/* For string conversions where numbers are also acceptable */ +#define DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(name,type,max) \ + int name##_to_string_alloc(type i, char **str) { \ + char *s; \ + int r; \ + if (i < 0 || i > max) \ + return -ERANGE; \ + if (i < (type) ELEMENTSOF(name##_table)) { \ + s = strdup(name##_table[i]); \ + if (!s) \ + return log_oom(); \ + } else { \ + r = asprintf(&s, "%u", i); \ + if (r < 0) \ + return log_oom(); \ + } \ + *str = s; \ + return 0; \ + } \ + type name##_from_string(const char *s) { \ + type i; \ + unsigned u = 0; \ + assert(s); \ + for (i = 0; i < (type)ELEMENTSOF(name##_table); i++) \ + if (name##_table[i] && \ + streq(name##_table[i], s)) \ + return i; \ + if (safe_atou(s, &u) >= 0 && u <= max) \ + return (type) u; \ + return (type) -1; \ + } \ + struct __useless_struct_to_allow_trailing_semicolon__ + +int fd_nonblock(int fd, bool nonblock); +int fd_cloexec(int fd, bool cloexec); + +int close_all_fds(const int except[], unsigned n_except); + +bool fstype_is_network(const char *fstype); + +int chvt(int vt); + +int read_one_char(FILE *f, char *ret, usec_t timeout, bool *need_nl); +int ask(char *ret, const char *replies, const char *text, ...) _printf_attr_(3, 4); + +int reset_terminal_fd(int fd, bool switch_to_text); +int reset_terminal(const char *name); + +int open_terminal(const char *name, int mode); +int acquire_terminal(const char *name, bool fail, bool force, bool ignore_tiocstty_eperm, usec_t timeout); +int release_terminal(void); + +int flush_fd(int fd); + +int ignore_signals(int sig, ...); +int default_signals(int sig, ...); +int sigaction_many(const struct sigaction *sa, ...); + +int close_pipe(int p[]); +int fopen_temporary(const char *path, FILE **_f, char **_temp_path); + +ssize_t loop_read(int fd, void *buf, size_t nbytes, bool do_poll); +ssize_t loop_write(int fd, const void *buf, size_t nbytes, bool do_poll); + +bool is_device_path(const char *path); + +int dir_is_empty(const char *path); +char* dirname_malloc(const char *path); + +void rename_process(const char name[8]); + +void sigset_add_many(sigset_t *ss, ...); + +bool hostname_is_set(void); + +char* gethostname_malloc(void); +char* getlogname_malloc(void); +char* getusername_malloc(void); + +int getttyname_malloc(int fd, char **r); +int getttyname_harder(int fd, char **r); + +int get_ctty_devnr(pid_t pid, dev_t *d); +int get_ctty(pid_t, dev_t *_devnr, char **r); + +int chmod_and_chown(const char *path, mode_t mode, uid_t uid, gid_t gid); +int fchmod_and_fchown(int fd, mode_t mode, uid_t uid, gid_t gid); + +int rm_rf_children(int fd, bool only_dirs, bool honour_sticky, struct stat *root_dev); +int rm_rf_children_dangerous(int fd, bool only_dirs, bool honour_sticky, struct stat *root_dev); +int rm_rf(const char *path, bool only_dirs, bool delete_root, bool honour_sticky); +int rm_rf_dangerous(const char *path, bool only_dirs, bool delete_root, bool honour_sticky); + +int pipe_eof(int fd); + +cpu_set_t* cpu_set_malloc(unsigned *ncpus); + +int status_vprintf(const char *status, bool ellipse, bool ephemeral, const char *format, va_list ap) _printf_attr_(4,0); +int status_printf(const char *status, bool ellipse, bool ephemeral, const char *format, ...) _printf_attr_(4,5); +int status_welcome(void); + +int fd_columns(int fd); +unsigned columns(void); +int fd_lines(int fd); +unsigned lines(void); +void columns_lines_cache_reset(int _unused_ signum); + +bool on_tty(void); + +int running_in_chroot(void); + +char *ellipsize(const char *s, size_t length, unsigned percent); +char *ellipsize_mem(const char *s, size_t old_length, size_t new_length, unsigned percent); + +int touch(const char *path); + +char *unquote(const char *s, const char *quotes); +char *normalize_env_assignment(const char *s); + +int wait_for_terminate(pid_t pid, siginfo_t *status); +int wait_for_terminate_and_warn(const char *name, pid_t pid); + +_noreturn_ void freeze(void); + +bool null_or_empty(struct stat *st) _pure_; +int null_or_empty_path(const char *fn); + +DIR *xopendirat(int dirfd, const char *name, int flags); + +char *fstab_node_to_udev_node(const char *p); + +char *resolve_dev_console(char **active); +bool tty_is_vc(const char *tty); +bool tty_is_vc_resolve(const char *tty); +bool tty_is_console(const char *tty) _pure_; +int vtnr_from_tty(const char *tty); +const char *default_term_for_tty(const char *tty); + +void execute_directory(const char *directory, DIR *_d, char *argv[]); + +int kill_and_sigcont(pid_t pid, int sig); + +bool nulstr_contains(const char*nulstr, const char *needle); + +bool plymouth_running(void); + +bool hostname_is_valid(const char *s) _pure_; +char* hostname_cleanup(char *s, bool lowercase); + +char* strshorten(char *s, size_t l); + +int terminal_vhangup_fd(int fd); +int terminal_vhangup(const char *name); + +int vt_disallocate(const char *name); + +int copy_file(const char *from, const char *to); + +int symlink_atomic(const char *from, const char *to); + +int fchmod_umask(int fd, mode_t mode); + +bool display_is_local(const char *display) _pure_; +int socket_from_display(const char *display, char **path); + +int get_user_creds(const char **username, uid_t *uid, gid_t *gid, const char **home, const char **shell); +int get_group_creds(const char **groupname, gid_t *gid); + +int in_gid(gid_t gid); +int in_group(const char *name); + +char* uid_to_name(uid_t uid); +char* gid_to_name(gid_t gid); + +int glob_exists(const char *path); + +int dirent_ensure_type(DIR *d, struct dirent *de); + +int in_search_path(const char *path, char **search); +int get_files_in_directory(const char *path, char ***list); + +char *strjoin(const char *x, ...) _sentinel_; + +bool is_main_thread(void); + +bool in_charset(const char *s, const char* charset) _pure_; + +int block_get_whole_disk(dev_t d, dev_t *ret); + +int file_is_priv_sticky(const char *p); + +int strdup_or_null(const char *a, char **b); + +#define NULSTR_FOREACH(i, l) \ + for ((i) = (l); (i) && *(i); (i) = strchr((i), 0)+1) + +#define NULSTR_FOREACH_PAIR(i, j, l) \ + for ((i) = (l), (j) = strchr((i), 0)+1; (i) && *(i); (i) = strchr((j), 0)+1, (j) = *(i) ? strchr((i), 0)+1 : (i)) + +int ioprio_class_to_string_alloc(int i, char **s); +int ioprio_class_from_string(const char *s); + +const char *sigchld_code_to_string(int i) _const_; +int sigchld_code_from_string(const char *s) _pure_; + +int log_facility_unshifted_to_string_alloc(int i, char **s); +int log_facility_unshifted_from_string(const char *s); + +int log_level_to_string_alloc(int i, char **s); +int log_level_from_string(const char *s); + +int sched_policy_to_string_alloc(int i, char **s); +int sched_policy_from_string(const char *s); + +const char *rlimit_to_string(int i) _const_; +int rlimit_from_string(const char *s) _pure_; + +int ip_tos_to_string_alloc(int i, char **s); +int ip_tos_from_string(const char *s); + +const char *signal_to_string(int i) _const_; +int signal_from_string(const char *s) _pure_; + +int signal_from_string_try_harder(const char *s); + +extern int saved_argc; +extern char **saved_argv; + +bool kexec_loaded(void); + +int prot_from_flags(int flags) _const_; + +char *format_bytes(char *buf, size_t l, off_t t); + +int fd_wait_for_event(int fd, int event, usec_t timeout); + +void* memdup(const void *p, size_t l) _alloc_(2); + +int is_kernel_thread(pid_t pid); + +int fd_inc_sndbuf(int fd, size_t n); +int fd_inc_rcvbuf(int fd, size_t n); + +int fork_agent(pid_t *pid, const int except[], unsigned n_except, const char *path, ...); + +int setrlimit_closest(int resource, const struct rlimit *rlim); + +int getenv_for_pid(pid_t pid, const char *field, char **_value); + +bool is_valid_documentation_url(const char *url) _pure_; + +bool in_initrd(void); + +void warn_melody(void); + +int get_home_dir(char **ret); + +static inline void freep(void *p) { + free(*(void**) p); +} + +static inline void fclosep(FILE **f) { + if (*f) + fclose(*f); +} + +static inline void pclosep(FILE **f) { + if (*f) + pclose(*f); +} + +static inline void closep(int *fd) { + if (*fd >= 0) + close_nointr_nofail(*fd); +} + +static inline void closedirp(DIR **d) { + if (*d) + closedir(*d); +} + +static inline void umaskp(mode_t *u) { + umask(*u); +} + +#define _cleanup_free_ _cleanup_(freep) +#define _cleanup_fclose_ _cleanup_(fclosep) +#define _cleanup_pclose_ _cleanup_(pclosep) +#define _cleanup_close_ _cleanup_(closep) +#define _cleanup_closedir_ _cleanup_(closedirp) +#define _cleanup_umask_ _cleanup_(umaskp) +#define _cleanup_globfree_ _cleanup_(globfree) + +_malloc_ _alloc_(1, 2) static inline void *malloc_multiply(size_t a, size_t b) { + if (_unlikely_(b == 0 || a > ((size_t) -1) / b)) + return NULL; + + return malloc(a * b); +} + +_alloc_(2, 3) static inline void *memdup_multiply(const void *p, size_t a, size_t b) { + if (_unlikely_(b == 0 || a > ((size_t) -1) / b)) + return NULL; + + return memdup(p, a * b); +} + +bool filename_is_safe(const char *p) _pure_; +bool path_is_safe(const char *p) _pure_; +bool string_is_safe(const char *p) _pure_; +bool string_has_cc(const char *p) _pure_; + +void *xbsearch_r(const void *key, const void *base, size_t nmemb, size_t size, + int (*compar) (const void *, const void *, void *), + void *arg); + +bool is_locale_utf8(void); + +typedef enum DrawSpecialChar { + DRAW_TREE_VERT, + DRAW_TREE_BRANCH, + DRAW_TREE_RIGHT, + DRAW_TREE_SPACE, + DRAW_TRIANGULAR_BULLET, + _DRAW_SPECIAL_CHAR_MAX +} DrawSpecialChar; +const char *draw_special_char(DrawSpecialChar ch); + +char *strreplace(const char *text, const char *old_string, const char *new_string); + +char *strip_tab_ansi(char **p, size_t *l); + +int on_ac_power(void); + +int search_and_fopen(const char *path, const char *mode, const char **search, FILE **_f); +int search_and_fopen_nulstr(const char *path, const char *mode, const char *search, FILE **_f); +int create_tmp_dir(char template[], char** dir_name); + +#define FOREACH_LINE(line, f, on_error) \ + for (;;) \ + if (!fgets(line, sizeof(line), f)) { \ + if (ferror(f)) { \ + on_error; \ + } \ + break; \ + } else + +#define FOREACH_DIRENT(de, d, on_error) \ + for (errno = 0, de = readdir(d);; errno = 0, de = readdir(d)) \ + if (!de) { \ + if (errno > 0) { \ + on_error; \ + } \ + break; \ + } else if (ignore_file((de)->d_name)) \ + continue; \ + else + +static inline void *mempset(void *s, int c, size_t n) { + memset(s, c, n); + return (uint8_t*)s + n; +} + +char *hexmem(const void *p, size_t l); +void *unhexmem(const char *p, size_t l); + +char *strextend(char **x, ...) _sentinel_; +char *strrep(const char *s, unsigned n); + +void* greedy_realloc(void **p, size_t *allocated, size_t need); +#define GREEDY_REALLOC(array, allocated, need) \ + greedy_realloc((void**) &(array), &(allocated), sizeof((array)[0]) * (need)) + +static inline void _reset_errno_(int *saved_errno) { + errno = *saved_errno; +} + +#define PROTECT_ERRNO _cleanup_(_reset_errno_) __attribute__((unused)) int _saved_errno_ = errno + +struct _umask_struct_ { + mode_t mask; + bool quit; +}; + +static inline void _reset_umask_(struct _umask_struct_ *s) { + umask(s->mask); +}; + +#define RUN_WITH_UMASK(mask) \ + for (_cleanup_(_reset_umask_) struct _umask_struct_ _saved_umask_ = { umask(mask), false }; \ + !_saved_umask_.quit ; \ + _saved_umask_.quit = true) + +static inline unsigned u64log2(uint64_t n) { + return (n > 1) ? __builtin_clzll(n) ^ 63U : 0; +} + +static inline bool logind_running(void) { + return access("/run/systemd/seats/", F_OK) >= 0; +} + +#define DECIMAL_STR_WIDTH(x) \ + ({ \ + typeof(x) _x_ = (x); \ + unsigned ans = 1; \ + while (_x_ /= 10) \ + ans++; \ + ans; \ + }) + +int unlink_noerrno(const char *path); + +#define alloca0(n) \ + ({ \ + char *_new_; \ + size_t _len_ = n; \ + _new_ = alloca(_len_); \ + (void *) memset(_new_, 0, _len_); \ + }) + +#define strappenda(a, b) \ + ({ \ + const char *_a_ = (a), *_b_ = (b); \ + char *_c_; \ + size_t _x_, _y_; \ + _x_ = strlen(_a_); \ + _y_ = strlen(_b_); \ + _c_ = alloca(_x_ + _y_ + 1); \ + strcpy(stpcpy(_c_, _a_), _b_); \ + _c_; \ + }) + +#define procfs_file_alloca(pid, field) \ + ({ \ + pid_t _pid_ = (pid); \ + char *_r_; \ + _r_ = alloca(sizeof("/proc/") -1 + DECIMAL_STR_MAX(pid_t) + 1 + sizeof(field)); \ + sprintf(_r_, "/proc/%lu/" field, (unsigned long) _pid_); \ + _r_; \ + }) + +struct _locale_struct_ { + locale_t saved_locale; + locale_t new_locale; + bool quit; +}; + +static inline void _reset_locale_(struct _locale_struct_ *s) { + PROTECT_ERRNO; + if (s->saved_locale != (locale_t) 0) + uselocale(s->saved_locale); + if (s->new_locale != (locale_t) 0) + freelocale(s->new_locale); +} + +#define RUN_WITH_LOCALE(mask, loc) \ + for (_cleanup_(_reset_locale_) struct _locale_struct_ _saved_locale_ = { (locale_t) 0, (locale_t) 0, false }; \ + ({ \ + if (!_saved_locale_.quit) { \ + PROTECT_ERRNO; \ + _saved_locale_.new_locale = newlocale((mask), (loc), (locale_t) 0); \ + if (_saved_locale_.new_locale != (locale_t) 0) \ + _saved_locale_.saved_locale = uselocale(_saved_locale_.new_locale); \ + } \ + !_saved_locale_.quit; }) ; \ + _saved_locale_.quit = true) + +bool id128_is_valid(const char *s) _pure_; diff --git a/src/shared/utmp-wtmp.c b/src/shared/utmp-wtmp.c new file mode 100644 index 0000000000..5d88405e13 --- /dev/null +++ b/src/shared/utmp-wtmp.c @@ -0,0 +1,410 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <utmpx.h> +#include <errno.h> +#include <assert.h> +#include <string.h> +#include <sys/utsname.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/poll.h> + +#include "macro.h" +#include "path-util.h" +#include "utmp-wtmp.h" + +int utmp_get_runlevel(int *runlevel, int *previous) { + struct utmpx *found, lookup = { .ut_type = RUN_LVL }; + int r; + const char *e; + + assert(runlevel); + + /* If these values are set in the environment this takes + * precedence. Presumably, sysvinit does this to work around a + * race condition that would otherwise exist where we'd always + * go to disk and hence might read runlevel data that might be + * very new and does not apply to the current script being + * executed. */ + + if ((e = getenv("RUNLEVEL")) && e[0] > 0) { + *runlevel = e[0]; + + if (previous) { + /* $PREVLEVEL seems to be an Upstart thing */ + + if ((e = getenv("PREVLEVEL")) && e[0] > 0) + *previous = e[0]; + else + *previous = 0; + } + + return 0; + } + + if (utmpxname(_PATH_UTMPX) < 0) + return -errno; + + setutxent(); + + if (!(found = getutxid(&lookup))) + r = -errno; + else { + int a, b; + + a = found->ut_pid & 0xFF; + b = (found->ut_pid >> 8) & 0xFF; + + *runlevel = a; + if (previous) + *previous = b; + + r = 0; + } + + endutxent(); + + return r; +} + +static void init_timestamp(struct utmpx *store, usec_t t) { + assert(store); + + zero(*store); + + if (t <= 0) + t = now(CLOCK_REALTIME); + + store->ut_tv.tv_sec = t / USEC_PER_SEC; + store->ut_tv.tv_usec = t % USEC_PER_SEC; +} + +static void init_entry(struct utmpx *store, usec_t t) { + struct utsname uts = {}; + + assert(store); + + init_timestamp(store, t); + + if (uname(&uts) >= 0) + strncpy(store->ut_host, uts.release, sizeof(store->ut_host)); + + strncpy(store->ut_line, "~", sizeof(store->ut_line)); /* or ~~ ? */ + strncpy(store->ut_id, "~~", sizeof(store->ut_id)); +} + +static int write_entry_utmp(const struct utmpx *store) { + int r; + + assert(store); + + /* utmp is similar to wtmp, but there is only one entry for + * each entry type resp. user; i.e. basically a key/value + * table. */ + + if (utmpxname(_PATH_UTMPX) < 0) + return -errno; + + setutxent(); + + if (!pututxline(store)) + r = -errno; + else + r = 0; + + endutxent(); + + return r; +} + +static int write_entry_wtmp(const struct utmpx *store) { + assert(store); + + /* wtmp is a simple append-only file where each entry is + simply appended to * the end; i.e. basically a log. */ + + errno = 0; + updwtmpx(_PATH_WTMPX, store); + return -errno; +} + +static int write_utmp_wtmp(const struct utmpx *store_utmp, const struct utmpx *store_wtmp) { + int r, s; + + r = write_entry_utmp(store_utmp); + s = write_entry_wtmp(store_wtmp); + + if (r >= 0) + r = s; + + /* If utmp/wtmp have been disabled, that's a good thing, hence + * ignore the errors */ + if (r == -ENOENT) + r = 0; + + return r; +} + +static int write_entry_both(const struct utmpx *store) { + return write_utmp_wtmp(store, store); +} + +int utmp_put_shutdown(void) { + struct utmpx store; + + init_entry(&store, 0); + + store.ut_type = RUN_LVL; + strncpy(store.ut_user, "shutdown", sizeof(store.ut_user)); + + return write_entry_both(&store); +} + +int utmp_put_reboot(usec_t t) { + struct utmpx store; + + init_entry(&store, t); + + store.ut_type = BOOT_TIME; + strncpy(store.ut_user, "reboot", sizeof(store.ut_user)); + + return write_entry_both(&store); +} + +_pure_ static const char *sanitize_id(const char *id) { + size_t l; + + assert(id); + l = strlen(id); + + if (l <= sizeof(((struct utmpx*) NULL)->ut_id)) + return id; + + return id + l - sizeof(((struct utmpx*) NULL)->ut_id); +} + +int utmp_put_init_process(const char *id, pid_t pid, pid_t sid, const char *line) { + struct utmpx store; + + assert(id); + + init_timestamp(&store, 0); + + store.ut_type = INIT_PROCESS; + store.ut_pid = pid; + store.ut_session = sid; + + strncpy(store.ut_id, sanitize_id(id), sizeof(store.ut_id)); + + if (line) + strncpy(store.ut_line, path_get_file_name(line), sizeof(store.ut_line)); + + return write_entry_both(&store); +} + +int utmp_put_dead_process(const char *id, pid_t pid, int code, int status) { + struct utmpx lookup, store, store_wtmp, *found; + + assert(id); + + setutxent(); + + zero(lookup); + lookup.ut_type = INIT_PROCESS; /* looks for DEAD_PROCESS, LOGIN_PROCESS, USER_PROCESS, too */ + strncpy(lookup.ut_id, sanitize_id(id), sizeof(lookup.ut_id)); + + if (!(found = getutxid(&lookup))) + return 0; + + if (found->ut_pid != pid) + return 0; + + memcpy(&store, found, sizeof(store)); + store.ut_type = DEAD_PROCESS; + store.ut_exit.e_termination = code; + store.ut_exit.e_exit = status; + + zero(store.ut_user); + zero(store.ut_host); + zero(store.ut_tv); + + memcpy(&store_wtmp, &store, sizeof(store_wtmp)); + /* wtmp wants the current time */ + init_timestamp(&store_wtmp, 0); + + return write_utmp_wtmp(&store, &store_wtmp); +} + + +int utmp_put_runlevel(int runlevel, int previous) { + struct utmpx store; + int r; + + assert(runlevel > 0); + + if (previous <= 0) { + /* Find the old runlevel automatically */ + + if ((r = utmp_get_runlevel(&previous, NULL)) < 0) { + if (r != -ESRCH) + return r; + + previous = 0; + } + } + + if (previous == runlevel) + return 0; + + init_entry(&store, 0); + + store.ut_type = RUN_LVL; + store.ut_pid = (runlevel & 0xFF) | ((previous & 0xFF) << 8); + strncpy(store.ut_user, "runlevel", sizeof(store.ut_user)); + + return write_entry_both(&store); +} + +#define TIMEOUT_MSEC 50 + +static int write_to_terminal(const char *tty, const char *message) { + _cleanup_close_ int fd = -1; + const char *p; + size_t left; + usec_t end; + + assert(tty); + assert(message); + + fd = open(tty, O_WRONLY|O_NDELAY|O_NOCTTY|O_CLOEXEC); + if (fd < 0 || !isatty(fd)) + return -errno; + + p = message; + left = strlen(message); + + end = now(CLOCK_MONOTONIC) + TIMEOUT_MSEC*USEC_PER_MSEC; + + while (left > 0) { + ssize_t n; + struct pollfd pollfd = { + .fd = fd, + .events = POLLOUT, + }; + usec_t t; + int k; + + t = now(CLOCK_MONOTONIC); + + if (t >= end) + return -ETIME; + + k = poll(&pollfd, 1, (end - t) / USEC_PER_MSEC); + if (k < 0) + return -errno; + + if (k == 0) + return -ETIME; + + n = write(fd, p, left); + if (n < 0) { + if (errno == EAGAIN) + continue; + + return -errno; + } + + assert((size_t) n <= left); + + p += n; + left -= n; + } + + return 0; +} + +int utmp_wall(const char *message, bool (*match_tty)(const char *tty)) { + struct utmpx *u; + char date[FORMAT_TIMESTAMP_MAX]; + char *text = NULL, *hn = NULL, *un = NULL, *tty = NULL; + int r; + + if (!(hn = gethostname_malloc()) || + !(un = getlogname_malloc())) { + r = -ENOMEM; + goto finish; + } + + getttyname_harder(STDIN_FILENO, &tty); + + if (asprintf(&text, + "\a\r\n" + "Broadcast message from %s@%s%s%s (%s):\r\n\r\n" + "%s\r\n\r\n", + un, hn, + tty ? " on " : "", strempty(tty), + format_timestamp(date, sizeof(date), now(CLOCK_REALTIME)), + message) < 0) { + r = -ENOMEM; + goto finish; + } + + setutxent(); + + r = 0; + + while ((u = getutxent())) { + int q; + const char *path; + char *buf = NULL; + + if (u->ut_type != USER_PROCESS || u->ut_user[0] == 0) + continue; + + /* this access is fine, because strlen("/dev/") << 32 (UT_LINESIZE) */ + if (path_startswith(u->ut_line, "/dev/")) + path = u->ut_line; + else { + if (asprintf(&buf, "/dev/%.*s", + (int) sizeof(u->ut_line), u->ut_line) < 0) { + r = -ENOMEM; + goto finish; + } + + path = buf; + } + + if (!match_tty || match_tty(path)) + if ((q = write_to_terminal(path, text)) < 0) + r = q; + + free(buf); + } + +finish: + free(hn); + free(un); + free(tty); + free(text); + + return r; +} diff --git a/src/shared/utmp-wtmp.h b/src/shared/utmp-wtmp.h new file mode 100644 index 0000000000..5924023203 --- /dev/null +++ b/src/shared/utmp-wtmp.h @@ -0,0 +1,35 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "util.h" + +int utmp_get_runlevel(int *runlevel, int *previous); + +int utmp_put_shutdown(void); +int utmp_put_reboot(usec_t timestamp); +int utmp_put_runlevel(int runlevel, int previous); + +int utmp_put_dead_process(const char *id, pid_t pid, int code, int status); +int utmp_put_init_process(const char *id, pid_t pid, pid_t sid, const char *line); + +int utmp_wall(const char *message, bool (*match_tty)(const char *tty)); diff --git a/src/shared/virt.c b/src/shared/virt.c new file mode 100644 index 0000000000..1c86a3dd1e --- /dev/null +++ b/src/shared/virt.c @@ -0,0 +1,272 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <string.h> +#include <errno.h> +#include <unistd.h> + +#include "util.h" +#include "virt.h" +#include "fileio.h" + +/* Returns a short identifier for the various VM implementations */ +int detect_vm(const char **id) { + +#if defined(__i386__) || defined(__x86_64__) + + /* Both CPUID and DMI are x86 specific interfaces... */ + + static const char *const dmi_vendors[] = { + "/sys/class/dmi/id/sys_vendor", + "/sys/class/dmi/id/board_vendor", + "/sys/class/dmi/id/bios_vendor" + }; + + static const char dmi_vendor_table[] = + "QEMU\0" "qemu\0" + /* http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=1009458 */ + "VMware\0" "vmware\0" + "VMW\0" "vmware\0" + "Microsoft Corporation\0" "microsoft\0" + "innotek GmbH\0" "oracle\0" + "Xen\0" "xen\0" + "Bochs\0" "bochs\0"; + + static const char cpuid_vendor_table[] = + "XenVMMXenVMM\0" "xen\0" + "KVMKVMKVM\0" "kvm\0" + /* http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=1009458 */ + "VMwareVMware\0" "vmware\0" + /* http://msdn.microsoft.com/en-us/library/ff542428.aspx */ + "Microsoft Hv\0" "microsoft\0"; + + uint32_t eax, ecx; + union { + uint32_t sig32[3]; + char text[13]; + } sig = {}; + unsigned i; + const char *j, *k; + bool hypervisor; + _cleanup_free_ char *hvtype = NULL; + int r; + + /* Try high-level hypervisor sysfs file first: + * + * https://bugs.freedesktop.org/show_bug.cgi?id=61491 */ + r = read_one_line_file("/sys/hypervisor/type", &hvtype); + if (r >= 0) { + if (streq(hvtype, "xen")) { + if (id) + *id = "xen"; + + return 1; + } + } else if (r != -ENOENT) + return r; + + /* http://lwn.net/Articles/301888/ */ + +#if defined (__i386__) +#define REG_a "eax" +#define REG_b "ebx" +#elif defined (__amd64__) +#define REG_a "rax" +#define REG_b "rbx" +#endif + + /* First detect whether there is a hypervisor */ + eax = 1; + __asm__ __volatile__ ( + /* ebx/rbx is being used for PIC! */ + " push %%"REG_b" \n\t" + " cpuid \n\t" + " pop %%"REG_b" \n\t" + + : "=a" (eax), "=c" (ecx) + : "0" (eax) + ); + + hypervisor = !!(ecx & 0x80000000U); + + if (hypervisor) { + + /* There is a hypervisor, see what it is */ + eax = 0x40000000U; + __asm__ __volatile__ ( + /* ebx/rbx is being used for PIC! */ + " push %%"REG_b" \n\t" + " cpuid \n\t" + " mov %%ebx, %1 \n\t" + " pop %%"REG_b" \n\t" + + : "=a" (eax), "=r" (sig.sig32[0]), "=c" (sig.sig32[1]), "=d" (sig.sig32[2]) + : "0" (eax) + ); + + NULSTR_FOREACH_PAIR(j, k, cpuid_vendor_table) + if (streq(sig.text, j)) { + + if (id) + *id = k; + + return 1; + } + } + + for (i = 0; i < ELEMENTSOF(dmi_vendors); i++) { + _cleanup_free_ char *s = NULL; + const char *found = NULL; + + r = read_one_line_file(dmi_vendors[i], &s); + if (r < 0) { + if (r != -ENOENT) + return r; + + continue; + } + + NULSTR_FOREACH_PAIR(j, k, dmi_vendor_table) + if (startswith(s, j)) + found = k; + + if (found) { + if (id) + *id = found; + + return 1; + } + } + + if (hypervisor || hvtype) { + if (id) + *id = "other"; + + return 1; + } + +#endif + return 0; +} + +int detect_container(const char **id) { + _cleanup_free_ char *e = NULL; + int r; + + /* Unfortunately many of these operations require root access + * in one way or another */ + + r = running_in_chroot(); + if (r < 0) + return r; + if (r > 0) { + + if (id) + *id = "chroot"; + + return 1; + } + + /* /proc/vz exists in container and outside of the container, + * /proc/bc only outside of the container. */ + if (access("/proc/vz", F_OK) >= 0 && + access("/proc/bc", F_OK) < 0) { + + if (id) + *id = "openvz"; + + return 1; + } + + r = getenv_for_pid(1, "container", &e); + if (r <= 0) + return r; + + /* We only recognize a selected few here, since we want to + * enforce a redacted namespace */ + if (streq(e, "lxc")) { + if (id) + *id = "lxc"; + } else if (streq(e, "lxc-libvirt")) { + if (id) + *id = "lxc-libvirt"; + } else if (streq(e, "systemd-nspawn")) { + if (id) + *id = "systemd-nspawn"; + } else { + if (id) + *id = "other"; + } + + return r; +} + +/* Returns a short identifier for the various VM/container implementations */ +Virtualization detect_virtualization(const char **id) { + + static __thread Virtualization cached_virt = _VIRTUALIZATION_INVALID; + static __thread const char *cached_id = NULL; + + const char *_id; + int r; + Virtualization v; + + if (_likely_(cached_virt >= 0)) { + + if (id && cached_virt > 0) + *id = cached_id; + + return cached_virt; + } + + r = detect_container(&_id); + if (r < 0) { + v = r; + goto finish; + } else if (r > 0) { + v = VIRTUALIZATION_CONTAINER; + goto finish; + } + + r = detect_vm(&_id); + if (r < 0) { + v = r; + goto finish; + } else if (r > 0) { + v = VIRTUALIZATION_VM; + goto finish; + } + + v = VIRTUALIZATION_NONE; + +finish: + if (v > 0) { + cached_id = _id; + + if (id) + *id = _id; + } + + if (v >= 0) + cached_virt = v; + + return v; +} diff --git a/src/shared/virt.h b/src/shared/virt.h new file mode 100644 index 0000000000..aa6ad35ba9 --- /dev/null +++ b/src/shared/virt.h @@ -0,0 +1,35 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +int detect_vm(const char **id); +int detect_container(const char **id); + +typedef enum Virtualization { + VIRTUALIZATION_NONE = 0, + VIRTUALIZATION_VM, + VIRTUALIZATION_CONTAINER, + _VIRTUALIZATION_MAX, + _VIRTUALIZATION_INVALID = -1 +} Virtualization; + +Virtualization detect_virtualization(const char **id); diff --git a/src/shared/watchdog.c b/src/shared/watchdog.c new file mode 100644 index 0000000000..ddbe7afd3c --- /dev/null +++ b/src/shared/watchdog.c @@ -0,0 +1,169 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2012 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <sys/ioctl.h> +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> +#include <linux/watchdog.h> + +#include "watchdog.h" +#include "log.h" + +static int watchdog_fd = -1; +static usec_t watchdog_timeout = (usec_t) -1; + +static int update_timeout(void) { + int r; + + if (watchdog_fd < 0) + return 0; + + if (watchdog_timeout == (usec_t) -1) + return 0; + else if (watchdog_timeout == 0) { + int flags; + + flags = WDIOS_DISABLECARD; + r = ioctl(watchdog_fd, WDIOC_SETOPTIONS, &flags); + if (r < 0) { + log_warning("Failed to disable hardware watchdog: %m"); + return -errno; + } + } else { + int sec, flags; + char buf[FORMAT_TIMESPAN_MAX]; + + sec = (int) ((watchdog_timeout + USEC_PER_SEC - 1) / USEC_PER_SEC); + r = ioctl(watchdog_fd, WDIOC_SETTIMEOUT, &sec); + if (r < 0) { + log_warning("Failed to set timeout to %is: %m", sec); + return -errno; + } + + watchdog_timeout = (usec_t) sec * USEC_PER_SEC; + log_info("Set hardware watchdog to %s.", format_timespan(buf, sizeof(buf), watchdog_timeout, 0)); + + flags = WDIOS_ENABLECARD; + r = ioctl(watchdog_fd, WDIOC_SETOPTIONS, &flags); + if (r < 0) { + log_warning("Failed to enable hardware watchdog: %m"); + return -errno; + } + + r = ioctl(watchdog_fd, WDIOC_KEEPALIVE, 0); + if (r < 0) { + log_warning("Failed to ping hardware watchdog: %m"); + return -errno; + } + } + + return 0; +} + +static int open_watchdog(void) { + struct watchdog_info ident; + + if (watchdog_fd >= 0) + return 0; + + watchdog_fd = open("/dev/watchdog", O_WRONLY|O_CLOEXEC); + if (watchdog_fd < 0) + return -errno; + + if (ioctl(watchdog_fd, WDIOC_GETSUPPORT, &ident) >= 0) + log_info("Hardware watchdog '%s', version %x", + ident.identity, + ident.firmware_version); + + return update_timeout(); +} + +int watchdog_set_timeout(usec_t *usec) { + int r; + + watchdog_timeout = *usec; + + /* If we didn't open the watchdog yet and didn't get any + * explicit timeout value set, don't do anything */ + if (watchdog_fd < 0 && watchdog_timeout == (usec_t) -1) + return 0; + + if (watchdog_fd < 0) + r = open_watchdog(); + else + r = update_timeout(); + + *usec = watchdog_timeout; + + return r; +} + +int watchdog_ping(void) { + int r; + + if (watchdog_fd < 0) { + r = open_watchdog(); + if (r < 0) + return r; + } + + r = ioctl(watchdog_fd, WDIOC_KEEPALIVE, 0); + if (r < 0) { + log_warning("Failed to ping hardware watchdog: %m"); + return -errno; + } + + return 0; +} + +void watchdog_close(bool disarm) { + int r; + + if (watchdog_fd < 0) + return; + + if (disarm) { + int flags; + + /* Explicitly disarm it */ + flags = WDIOS_DISABLECARD; + r = ioctl(watchdog_fd, WDIOC_SETOPTIONS, &flags); + if (r < 0) + log_warning("Failed to disable hardware watchdog: %m"); + + /* To be sure, use magic close logic, too */ + for (;;) { + static const char v = 'V'; + + if (write(watchdog_fd, &v, 1) > 0) + break; + + if (errno != EINTR) { + log_error("Failed to disarm watchdog timer: %m"); + break; + } + } + } + + close_nointr_nofail(watchdog_fd); + watchdog_fd = -1; +} diff --git a/src/shared/watchdog.h b/src/shared/watchdog.h new file mode 100644 index 0000000000..b748b15857 --- /dev/null +++ b/src/shared/watchdog.h @@ -0,0 +1,28 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2012 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "util.h" + +int watchdog_set_timeout(usec_t *usec); +int watchdog_ping(void); +void watchdog_close(bool disarm); |