diff options
Diffstat (limited to 'src')
103 files changed, 2656 insertions, 619 deletions
diff --git a/src/basic/architecture.c b/src/basic/architecture.c index a9ecfc1cd6..8e2c2b02d2 100644 --- a/src/basic/architecture.c +++ b/src/basic/architecture.c @@ -121,6 +121,8 @@ int uname_architecture(void) { { "tilegx", ARCHITECTURE_TILEGX }, #elif defined(__cris__) { "crisv32", ARCHITECTURE_CRIS }, +#elif defined(__nios2__) + { "nios2", ARCHITECTURE_NIOS2 }, #else #error "Please register your architecture here!" #endif @@ -171,6 +173,7 @@ static const char *const architecture_table[_ARCHITECTURE_MAX] = { [ARCHITECTURE_M68K] = "m68k", [ARCHITECTURE_TILEGX] = "tilegx", [ARCHITECTURE_CRIS] = "cris", + [ARCHITECTURE_NIOS2] = "nios2", }; DEFINE_STRING_TABLE_LOOKUP(architecture, int); diff --git a/src/basic/architecture.h b/src/basic/architecture.h index c22cbc8279..91ec108e04 100644 --- a/src/basic/architecture.h +++ b/src/basic/architecture.h @@ -57,6 +57,7 @@ enum { ARCHITECTURE_M68K, ARCHITECTURE_TILEGX, ARCHITECTURE_CRIS, + ARCHITECTURE_NIOS2, _ARCHITECTURE_MAX, _ARCHITECTURE_INVALID = -1 }; @@ -187,6 +188,9 @@ int uname_architecture(void); #elif defined(__cris__) # define native_architecture() ARCHITECTURE_CRIS # error "Missing LIB_ARCH_TUPLE for CRIS" +#elif defined(__nios2__) +# define native_architecture() ARCHITECTURE_NIOS2 +# define LIB_ARCH_TUPLE "nios2-linux-gnu" #else # error "Please register your architecture here!" #endif diff --git a/src/basic/copy.c b/src/basic/copy.c index e2db4be9ff..c3586728d0 100644 --- a/src/basic/copy.c +++ b/src/basic/copy.c @@ -102,7 +102,7 @@ int copy_bytes(int fdf, int fdt, uint64_t max_bytes, bool try_reflink) { if (try_cfr) { n = try_copy_file_range(fdf, NULL, fdt, NULL, m, 0u); if (n < 0) { - if (!IN_SET(n, -EINVAL, -ENOSYS, -EXDEV)) + if (!IN_SET(n, -EINVAL, -ENOSYS, -EXDEV, -EBADF)) return n; try_cfr = false; @@ -305,6 +305,8 @@ static int fd_copy_directory( fdf = openat(df, from, O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); else fdf = fcntl(df, F_DUPFD_CLOEXEC, 3); + if (fdf < 0) + return -errno; d = fdopendir(fdf); if (!d) @@ -325,22 +327,6 @@ static int fd_copy_directory( r = 0; - if (created) { - struct timespec ut[2] = { - st->st_atim, - st->st_mtim - }; - - if (fchown(fdt, st->st_uid, st->st_gid) < 0) - r = -errno; - - if (fchmod(fdt, st->st_mode & 07777) < 0) - r = -errno; - - (void) futimens(fdt, ut); - (void) copy_xattr(dirfd(d), fdt); - } - FOREACH_DIRENT_ALL(de, d, return -errno) { struct stat buf; int q; @@ -364,7 +350,7 @@ static int fd_copy_directory( q = fd_copy_symlink(dirfd(d), de->d_name, &buf, fdt, de->d_name); else if (S_ISFIFO(buf.st_mode)) q = fd_copy_fifo(dirfd(d), de->d_name, &buf, fdt, de->d_name); - else if (S_ISBLK(buf.st_mode) || S_ISCHR(buf.st_mode)) + else if (S_ISBLK(buf.st_mode) || S_ISCHR(buf.st_mode) || S_ISSOCK(buf.st_mode)) q = fd_copy_node(dirfd(d), de->d_name, &buf, fdt, de->d_name); else q = -EOPNOTSUPP; @@ -376,6 +362,22 @@ static int fd_copy_directory( r = q; } + if (created) { + struct timespec ut[2] = { + st->st_atim, + st->st_mtim + }; + + if (fchown(fdt, st->st_uid, st->st_gid) < 0) + r = -errno; + + if (fchmod(fdt, st->st_mode & 07777) < 0) + r = -errno; + + (void) copy_xattr(dirfd(d), fdt); + (void) futimens(fdt, ut); + } + return r; } @@ -396,7 +398,7 @@ int copy_tree_at(int fdf, const char *from, int fdt, const char *to, bool merge) return fd_copy_symlink(fdf, from, &st, fdt, to); else if (S_ISFIFO(st.st_mode)) return fd_copy_fifo(fdf, from, &st, fdt, to); - else if (S_ISBLK(st.st_mode) || S_ISCHR(st.st_mode)) + else if (S_ISBLK(st.st_mode) || S_ISCHR(st.st_mode) || S_ISSOCK(st.st_mode)) return fd_copy_node(fdf, from, &st, fdt, to); else return -EOPNOTSUPP; @@ -407,7 +409,6 @@ int copy_tree(const char *from, const char *to, bool merge) { } int copy_directory_fd(int dirfd, const char *to, bool merge) { - struct stat st; assert(dirfd >= 0); @@ -422,6 +423,21 @@ int copy_directory_fd(int dirfd, const char *to, bool merge) { return fd_copy_directory(dirfd, NULL, &st, AT_FDCWD, to, st.st_dev, merge); } +int copy_directory(const char *from, const char *to, bool merge) { + struct stat st; + + assert(from); + assert(to); + + if (lstat(from, &st) < 0) + return -errno; + + if (!S_ISDIR(st.st_mode)) + return -ENOTDIR; + + return fd_copy_directory(AT_FDCWD, from, &st, AT_FDCWD, to, st.st_dev, merge); +} + int copy_file_fd(const char *from, int fdt, bool try_reflink) { _cleanup_close_ int fdf = -1; int r; diff --git a/src/basic/copy.h b/src/basic/copy.h index 3e5eb52506..b5d08ebafe 100644 --- a/src/basic/copy.h +++ b/src/basic/copy.h @@ -30,6 +30,7 @@ int copy_file_atomic(const char *from, const char *to, mode_t mode, bool replace int copy_tree(const char *from, const char *to, bool merge); int copy_tree_at(int fdf, const char *from, int fdt, const char *to, bool merge); int copy_directory_fd(int dirfd, const char *to, bool merge); +int copy_directory(const char *from, const char *to, bool merge); int copy_bytes(int fdf, int fdt, uint64_t max_bytes, bool try_reflink); int copy_times(int fdf, int fdt); int copy_xattr(int fdf, int fdt); diff --git a/src/basic/dirent-util.c b/src/basic/dirent-util.c index 5fb535cb13..59067121b7 100644 --- a/src/basic/dirent-util.c +++ b/src/basic/dirent-util.c @@ -52,12 +52,10 @@ int dirent_ensure_type(DIR *d, struct dirent *de) { bool dirent_is_file(const struct dirent *de) { assert(de); - if (hidden_file(de->d_name)) + if (!IN_SET(de->d_type, DT_REG, DT_LNK, DT_UNKNOWN)) return false; - if (de->d_type != DT_REG && - de->d_type != DT_LNK && - de->d_type != DT_UNKNOWN) + if (hidden_or_backup_file(de->d_name)) return false; return true; @@ -66,12 +64,10 @@ bool dirent_is_file(const struct dirent *de) { 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) + if (!IN_SET(de->d_type, DT_REG, DT_LNK, DT_UNKNOWN)) return false; - if (hidden_file_allow_backup(de->d_name)) + if (de->d_name[0] == '.') return false; return endswith(de->d_name, suffix); diff --git a/src/basic/dirent-util.h b/src/basic/dirent-util.h index 6bf099b46c..b91d04908f 100644 --- a/src/basic/dirent-util.h +++ b/src/basic/dirent-util.h @@ -38,7 +38,7 @@ bool dirent_is_file_with_suffix(const struct dirent *de, const char *suffix) _pu on_error; \ } \ break; \ - } else if (hidden_file((de)->d_name)) \ + } else if (hidden_or_backup_file((de)->d_name)) \ continue; \ else diff --git a/src/basic/fd-util.c b/src/basic/fd-util.c index ec9560cd07..8b466cff15 100644 --- a/src/basic/fd-util.c +++ b/src/basic/fd-util.c @@ -25,11 +25,13 @@ #include <unistd.h> #include "fd-util.h" +#include "fs-util.h" #include "macro.h" #include "missing.h" #include "parse-util.h" #include "path-util.h" #include "socket-util.h" +#include "stdio-util.h" #include "util.h" int close_nointr(int fd) { @@ -229,7 +231,7 @@ int close_all_fds(const int except[], unsigned n_except) { while ((de = readdir(d))) { int fd = -1; - if (hidden_file(de->d_name)) + if (hidden_or_backup_file(de->d_name)) continue; if (safe_atoi(de->d_name, &fd) < 0) @@ -356,3 +358,17 @@ bool fdname_is_valid(const char *s) { return p - s < 256; } + +int fd_get_path(int fd, char **ret) { + char procfs_path[strlen("/proc/self/fd/") + DECIMAL_STR_MAX(int)]; + int r; + + xsprintf(procfs_path, "/proc/self/fd/%i", fd); + + r = readlink_malloc(procfs_path, ret); + + if (r == -ENOENT) /* If the file doesn't exist the fd is invalid */ + return -EBADF; + + return r; +} diff --git a/src/basic/fd-util.h b/src/basic/fd-util.h index 44528c6e35..b86e41698a 100644 --- a/src/basic/fd-util.h +++ b/src/basic/fd-util.h @@ -72,6 +72,8 @@ void cmsg_close_all(struct msghdr *mh); bool fdname_is_valid(const char *s); +int fd_get_path(int fd, char **ret); + /* Hint: ENETUNREACH happens if we try to connect to "non-existing" special IP addresses, such as ::5 */ #define ERRNO_IS_DISCONNECT(r) \ IN_SET(r, ENOTCONN, ECONNRESET, ECONNREFUSED, ECONNABORTED, EPIPE, ENETUNREACH) diff --git a/src/basic/fdset.c b/src/basic/fdset.c index 06f8ecbdbc..527f27bc67 100644 --- a/src/basic/fdset.c +++ b/src/basic/fdset.c @@ -151,7 +151,7 @@ int fdset_new_fill(FDSet **_s) { while ((de = readdir(d))) { int fd = -1; - if (hidden_file(de->d_name)) + if (hidden_or_backup_file(de->d_name)) continue; r = safe_atoi(de->d_name, &fd); diff --git a/src/basic/fs-util.c b/src/basic/fs-util.c index 51268828af..e24e7036f7 100644 --- a/src/basic/fs-util.c +++ b/src/basic/fs-util.c @@ -38,6 +38,7 @@ #include "mkdir.h" #include "parse-util.h" #include "path-util.h" +#include "stdio-util.h" #include "string-util.h" #include "strv.h" #include "time-util.h" @@ -493,3 +494,17 @@ int get_files_in_directory(const char *path, char ***list) { return n; } + +int inotify_add_watch_fd(int fd, int what, uint32_t mask) { + char path[strlen("/proc/self/fd/") + DECIMAL_STR_MAX(int) + 1]; + int r; + + /* This is like inotify_add_watch(), except that the file to watch is not referenced by a path, but by an fd */ + xsprintf(path, "/proc/self/fd/%i", what); + + r = inotify_add_watch(fd, path, mask); + if (r < 0) + return -errno; + + return r; +} diff --git a/src/basic/fs-util.h b/src/basic/fs-util.h index 0d23f8635f..517b599d6f 100644 --- a/src/basic/fs-util.h +++ b/src/basic/fs-util.h @@ -72,3 +72,5 @@ union inotify_event_buffer { struct inotify_event ev; uint8_t raw[INOTIFY_EVENT_MAX]; }; + +int inotify_add_watch_fd(int fd, int what, uint32_t mask); diff --git a/src/basic/hashmap.c b/src/basic/hashmap.c index 85b8d812b3..49a0479592 100644 --- a/src/basic/hashmap.c +++ b/src/basic/hashmap.c @@ -1773,20 +1773,18 @@ int set_consume(Set *s, void *value) { int set_put_strdup(Set *s, const char *p) { char *c; - int r; assert(s); assert(p); + if (set_contains(s, (char*) p)) + return 0; + c = strdup(p); if (!c) return -ENOMEM; - r = set_consume(s, c); - if (r == -EEXIST) - return 0; - - return r; + return set_consume(s, c); } int set_put_strdupv(Set *s, char **l) { diff --git a/src/basic/missing.h b/src/basic/missing.h index 6616f0b720..22ea8f67cc 100644 --- a/src/basic/missing.h +++ b/src/basic/missing.h @@ -445,6 +445,10 @@ struct btrfs_ioctl_quota_ctl_args { #define TMPFS_MAGIC 0x01021994 #endif +#ifndef MQUEUE_MAGIC +#define MQUEUE_MAGIC 0x19800202 +#endif + #ifndef MS_MOVE #define MS_MOVE 8192 #endif @@ -553,7 +557,7 @@ struct btrfs_ioctl_quota_ctl_args { #define IFLA_INET6_ADDR_GEN_MODE 8 #define __IFLA_INET6_MAX 9 -#define IFLA_INET6_MAX (__IFLA_INET6_MAX - 1) +#define IFLA_INET6_MAX (__IFLA_INET6_MAX - 1) #define IN6_ADDR_GEN_MODE_EUI64 0 #define IN6_ADDR_GEN_MODE_NONE 1 @@ -738,7 +742,7 @@ struct btrfs_ioctl_quota_ctl_args { #define IFLA_BRIDGE_MAX (__IFLA_BRIDGE_MAX - 1) #endif -#if !HAVE_DECL_IFLA_BR_PRIORITY +#if !HAVE_DECL_IFLA_BR_VLAN_DEFAULT_PVID #define IFLA_BR_UNSPEC 0 #define IFLA_BR_FORWARD_DELAY 1 #define IFLA_BR_HELLO_TIME 2 diff --git a/src/basic/mount-util.c b/src/basic/mount-util.c index 5faa2eba05..ba698959b7 100644 --- a/src/basic/mount-util.c +++ b/src/basic/mount-util.c @@ -498,7 +498,9 @@ bool fstype_is_network(const char *fstype) { "nfs4\0" "gfs\0" "gfs2\0" - "glusterfs\0"; + "glusterfs\0" + "pvfs2\0" /* OrangeFS */ + ; const char *x; diff --git a/src/basic/parse-util.h b/src/basic/parse-util.h index c407263e16..7dc579a159 100644 --- a/src/basic/parse-util.h +++ b/src/basic/parse-util.h @@ -93,7 +93,7 @@ static inline int safe_atoli(const char *s, long int *ret_u) { #if SIZE_MAX == UINT_MAX static inline int safe_atozu(const char *s, size_t *ret_u) { assert_cc(sizeof(size_t) == sizeof(unsigned)); - return safe_atou(s, ret_u); + return safe_atou(s, (unsigned *) ret_u); } #else static inline int safe_atozu(const char *s, size_t *ret_u) { diff --git a/src/basic/path-util.c b/src/basic/path-util.c index 044a12889d..b2fa81a294 100644 --- a/src/basic/path-util.c +++ b/src/basic/path-util.c @@ -756,34 +756,53 @@ char *file_in_same_dir(const char *path, const char *filename) { return ret; } -bool hidden_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, ".dpkg-tmp") || - endswith(filename, ".dpkg-dist") || - endswith(filename, ".dpkg-bak") || - endswith(filename, ".dpkg-backup") || - endswith(filename, ".dpkg-remove") || - endswith(filename, ".swp"); -} +bool hidden_or_backup_file(const char *filename) { + const char *p; -bool hidden_file(const char *filename) { assert(filename); - if (endswith(filename, "~")) + if (filename[0] == '.' || + streq(filename, "lost+found") || + streq(filename, "aquota.user") || + streq(filename, "aquota.group") || + endswith(filename, "~")) return true; - return hidden_file_allow_backup(filename); + p = strrchr(filename, '.'); + if (!p) + return false; + + /* Please, let's not add more entries to the list below. If external projects think it's a good idea to come up + * with always new suffixes and that everybody else should just adjust to that, then it really should be on + * them. Hence, in future, let's not add any more entries. Instead, let's ask those packages to instead adopt + * one of the generic suffixes/prefixes for hidden files or backups, possibly augmented with an additional + * string. Specifically: there's now: + * + * The generic suffixes "~" and ".bak" for backup files + * The generic prefix "." for hidden files + * + * Thus, if a new package manager "foopkg" wants its own set of ".foopkg-new", ".foopkg-old", ".foopkg-dist" + * or so registered, let's refuse that and ask them to use ".foopkg.new", ".foopkg.old" or ".foopkg~" instead. + */ + + return STR_IN_SET(p + 1, + "rpmnew", + "rpmsave", + "rpmorig", + "dpkg-old", + "dpkg-new", + "dpkg-tmp", + "dpkg-dist", + "dpkg-bak", + "dpkg-backup", + "dpkg-remove", + "ucf-new", + "ucf-old", + "ucf-dist", + "swp", + "bak", + "old", + "new"); } bool is_device_path(const char *path) { diff --git a/src/basic/path-util.h b/src/basic/path-util.h index 34d5cd1570..a27c13fcc3 100644 --- a/src/basic/path-util.h +++ b/src/basic/path-util.h @@ -122,7 +122,6 @@ bool path_is_safe(const char *p) _pure_; char *file_in_same_dir(const char *path, const char *filename); -bool hidden_file_allow_backup(const char *filename); -bool hidden_file(const char *filename) _pure_; +bool hidden_or_backup_file(const char *filename) _pure_; bool is_device_path(const char *path); diff --git a/src/basic/process-util.c b/src/basic/process-util.c index f2cea01979..4a7367cc92 100644 --- a/src/basic/process-util.c +++ b/src/basic/process-util.c @@ -528,14 +528,20 @@ int wait_for_terminate_and_warn(const char *name, pid_t pid, bool check_exit_cod return -EPROTO; } -void sigkill_wait(pid_t *pid) { +void sigkill_wait(pid_t pid) { + assert(pid > 1); + + if (kill(pid, SIGKILL) > 0) + (void) wait_for_terminate(pid, NULL); +} + +void sigkill_waitp(pid_t *pid) { if (!pid) return; if (*pid <= 1) return; - if (kill(*pid, SIGKILL) > 0) - (void) wait_for_terminate(*pid, NULL); + sigkill_wait(*pid); } int kill_and_sigcont(pid_t pid, int sig) { diff --git a/src/basic/process-util.h b/src/basic/process-util.h index ffd4bcb0ff..9f75088796 100644 --- a/src/basic/process-util.h +++ b/src/basic/process-util.h @@ -58,8 +58,8 @@ int get_process_ppid(pid_t pid, pid_t *ppid); int wait_for_terminate(pid_t pid, siginfo_t *status); int wait_for_terminate_and_warn(const char *name, pid_t pid, bool check_exit_code); -void sigkill_wait(pid_t *pid); -#define _cleanup_sigkill_wait_ _cleanup_(sigkill_wait) +void sigkill_wait(pid_t pid); +void sigkill_waitp(pid_t *pid); int kill_and_sigcont(pid_t pid, int sig); diff --git a/src/basic/rlimit-util.c b/src/basic/rlimit-util.c index 7540b43215..ee063720ed 100644 --- a/src/basic/rlimit-util.c +++ b/src/basic/rlimit-util.c @@ -153,6 +153,56 @@ static int rlimit_parse_usec(const char *val, rlim_t *ret) { return 0; } +static int rlimit_parse_nice(const char *val, rlim_t *ret) { + uint64_t rl; + int r; + + /* So, Linux is weird. The range for RLIMIT_NICE is 40..1, mapping to the nice levels -20..19. However, the + * RLIMIT_NICE limit defaults to 0 by the kernel, i.e. a value that maps to nice level 20, which of course is + * bogus and does not exist. In order to permit parsing the RLIMIT_NICE of 0 here we hence implement a slight + * asymmetry: when parsing as positive nice level we permit 0..19. When parsing as negative nice level, we + * permit -20..0. But when parsing as raw resource limit value then we also allow the special value 0. + * + * Yeah, Linux is quality engineering sometimes... */ + + if (val[0] == '+') { + + /* Prefixed with "+": Parse as positive user-friendly nice value */ + r = safe_atou64(val + 1, &rl); + if (r < 0) + return r; + + if (rl >= PRIO_MAX) + return -ERANGE; + + rl = 20 - rl; + + } else if (val[0] == '-') { + + /* Prefixed with "-": Parse as negative user-friendly nice value */ + r = safe_atou64(val + 1, &rl); + if (r < 0) + return r; + + if (rl > (uint64_t) (-PRIO_MIN)) + return -ERANGE; + + rl = 20 + rl; + } else { + + /* Not prefixed: parse as raw resource limit value */ + r = safe_atou64(val, &rl); + if (r < 0) + return r; + + if (rl > (uint64_t) (20 - PRIO_MIN)) + return -ERANGE; + } + + *ret = (rlim_t) rl; + return 0; +} + static int (*const rlimit_parse_table[_RLIMIT_MAX])(const char *val, rlim_t *ret) = { [RLIMIT_CPU] = rlimit_parse_sec, [RLIMIT_FSIZE] = rlimit_parse_size, @@ -167,7 +217,7 @@ static int (*const rlimit_parse_table[_RLIMIT_MAX])(const char *val, rlim_t *ret [RLIMIT_LOCKS] = rlimit_parse_u64, [RLIMIT_SIGPENDING] = rlimit_parse_u64, [RLIMIT_MSGQUEUE] = rlimit_parse_size, - [RLIMIT_NICE] = rlimit_parse_u64, + [RLIMIT_NICE] = rlimit_parse_nice, [RLIMIT_RTPRIO] = rlimit_parse_u64, [RLIMIT_RTTIME] = rlimit_parse_usec, }; diff --git a/src/basic/user-util.h b/src/basic/user-util.h index c23f1d485d..8026eca3f4 100644 --- a/src/basic/user-util.h +++ b/src/basic/user-util.h @@ -21,6 +21,7 @@ #include <stdbool.h> #include <sys/types.h> +#include <unistd.h> bool uid_is_valid(uid_t uid); @@ -63,3 +64,7 @@ int take_etc_passwd_lock(const char *root); #define PTR_TO_GID(p) ((gid_t) (((uintptr_t) (p))-1)) #define GID_TO_PTR(u) ((void*) (((uintptr_t) (u))+1)) + +static inline bool userns_supported(void) { + return access("/proc/self/uid_map", F_OK) >= 0; +} diff --git a/src/basic/util.c b/src/basic/util.c index b70c50047f..756c663be4 100644 --- a/src/basic/util.c +++ b/src/basic/util.c @@ -522,7 +522,7 @@ int on_ac_power(void) { if (!de) break; - if (hidden_file(de->d_name)) + if (hidden_or_backup_file(de->d_name)) continue; device = openat(dirfd(d), de->d_name, O_DIRECTORY|O_RDONLY|O_CLOEXEC|O_NOCTTY); diff --git a/src/cgtop/cgtop.c b/src/cgtop/cgtop.c index 9c0e82ebb3..14eb46c8db 100644 --- a/src/cgtop/cgtop.c +++ b/src/cgtop/cgtop.c @@ -362,7 +362,7 @@ static int refresh_one( Group **ret) { _cleanup_closedir_ DIR *d = NULL; - Group *ours; + Group *ours = NULL; int r; assert(controller); diff --git a/src/core/automount.c b/src/core/automount.c index 7c55d7bc49..1239a0efc6 100644 --- a/src/core/automount.c +++ b/src/core/automount.c @@ -1050,9 +1050,6 @@ const UnitVTable automount_vtable = { "Automount\0" "Install\0", - .no_alias = true, - .no_instances = true, - .init = automount_init, .load = automount_load, .done = automount_done, diff --git a/src/core/busname.c b/src/core/busname.c index f4f433340c..e7b7b5c012 100644 --- a/src/core/busname.c +++ b/src/core/busname.c @@ -1028,9 +1028,6 @@ const UnitVTable busname_vtable = { "Install\0", .private_section = "BusName", - .no_alias = true, - .no_instances = true, - .init = busname_init, .done = busname_done, .load = busname_load, diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c index d2eb388f7c..d45f511489 100644 --- a/src/core/dbus-manager.c +++ b/src/core/dbus-manager.c @@ -889,7 +889,7 @@ static int method_reset_failed(sd_bus_message *message, void *userdata, sd_bus_e return sd_bus_reply_method_return(message, NULL); } -static int list_units_filtered(sd_bus_message *message, void *userdata, sd_bus_error *error, char **states) { +static int list_units_filtered(sd_bus_message *message, void *userdata, sd_bus_error *error, char **states, char **patterns) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; Manager *m = userdata; const char *k; @@ -929,6 +929,10 @@ static int list_units_filtered(sd_bus_message *message, void *userdata, sd_bus_e !strv_contains(states, unit_sub_state_to_string(u))) continue; + if (!strv_isempty(patterns) && + !strv_fnmatch_or_empty(patterns, u->id, FNM_NOESCAPE)) + continue; + unit_path = unit_dbus_path(u); if (!unit_path) return -ENOMEM; @@ -963,7 +967,7 @@ static int list_units_filtered(sd_bus_message *message, void *userdata, sd_bus_e } static int method_list_units(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return list_units_filtered(message, userdata, error, NULL); + return list_units_filtered(message, userdata, error, NULL, NULL); } static int method_list_units_filtered(sd_bus_message *message, void *userdata, sd_bus_error *error) { @@ -974,7 +978,23 @@ static int method_list_units_filtered(sd_bus_message *message, void *userdata, s if (r < 0) return r; - return list_units_filtered(message, userdata, error, states); + return list_units_filtered(message, userdata, error, states, NULL); +} + +static int method_list_units_by_patterns(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_strv_free_ char **states = NULL; + _cleanup_strv_free_ char **patterns = NULL; + int r; + + r = sd_bus_message_read_strv(message, &states); + if (r < 0) + return r; + + r = sd_bus_message_read_strv(message, &patterns); + if (r < 0) + return r; + + return list_units_filtered(message, userdata, error, states, patterns); } static int method_list_jobs(sd_bus_message *message, void *userdata, sd_bus_error *error) { @@ -1465,7 +1485,7 @@ static int method_set_exit_code(sd_bus_message *message, void *userdata, sd_bus_ return sd_bus_reply_method_return(message, NULL); } -static int method_list_unit_files(sd_bus_message *message, void *userdata, sd_bus_error *error) { +static int list_unit_files_by_patterns(sd_bus_message *message, void *userdata, sd_bus_error *error, char **states, char **patterns) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; Manager *m = userdata; UnitFileList *item; @@ -1490,7 +1510,7 @@ static int method_list_unit_files(sd_bus_message *message, void *userdata, sd_bu if (!h) return -ENOMEM; - r = unit_file_get_list(m->unit_file_scope, NULL, h); + r = unit_file_get_list(m->unit_file_scope, NULL, h, states, patterns); if (r < 0) goto fail; @@ -1518,6 +1538,26 @@ fail: return r; } +static int method_list_unit_files(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return list_unit_files_by_patterns(message, userdata, error, NULL, NULL); +} + +static int method_list_unit_files_by_patterns(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_strv_free_ char **states = NULL; + _cleanup_strv_free_ char **patterns = NULL; + int r; + + r = sd_bus_message_read_strv(message, &states); + if (r < 0) + return r; + + r = sd_bus_message_read_strv(message, &patterns); + if (r < 0) + return r; + + return list_unit_files_by_patterns(message, userdata, error, states, patterns); +} + static int method_get_unit_file_state(sd_bus_message *message, void *userdata, sd_bus_error *error) { Manager *m = userdata; const char *name; @@ -2010,7 +2050,8 @@ const sd_bus_vtable bus_manager_vtable[] = { SD_BUS_PROPERTY("DefaultTimeoutStartUSec", "t", bus_property_get_usec, offsetof(Manager, default_timeout_start_usec), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("DefaultTimeoutStopUSec", "t", bus_property_get_usec, offsetof(Manager, default_timeout_stop_usec), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("DefaultRestartUSec", "t", bus_property_get_usec, offsetof(Manager, default_restart_usec), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultStartLimitInterval", "t", bus_property_get_usec, offsetof(Manager, default_start_limit_interval), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultStartLimitIntervalSec", "t", bus_property_get_usec, offsetof(Manager, default_start_limit_interval), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultStartLimitInterval", "t", bus_property_get_usec, offsetof(Manager, default_start_limit_interval), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN), /* obsolete alias name */ SD_BUS_PROPERTY("DefaultStartLimitBurst", "u", bus_property_get_unsigned, offsetof(Manager, default_start_limit_burst), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("DefaultCPUAccounting", "b", bus_property_get_bool, offsetof(Manager, default_cpu_accounting), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("DefaultBlockIOAccounting", "b", bus_property_get_bool, offsetof(Manager, default_blockio_accounting), SD_BUS_VTABLE_PROPERTY_CONST), @@ -2073,6 +2114,7 @@ const sd_bus_vtable bus_manager_vtable[] = { SD_BUS_METHOD("ResetFailed", NULL, NULL, method_reset_failed, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("ListUnits", NULL, "a(ssssssouso)", method_list_units, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("ListUnitsFiltered", "as", "a(ssssssouso)", method_list_units_filtered, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("ListUnitsByPatterns", "asas", "a(ssssssouso)", method_list_units_by_patterns, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("ListJobs", NULL, "a(usssoo)", method_list_jobs, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("Subscribe", NULL, NULL, method_subscribe, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("Unsubscribe", NULL, NULL, method_unsubscribe, SD_BUS_VTABLE_UNPRIVILEGED), @@ -2091,6 +2133,7 @@ const sd_bus_vtable bus_manager_vtable[] = { SD_BUS_METHOD("UnsetEnvironment", "as", NULL, method_unset_environment, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("UnsetAndSetEnvironment", "asas", NULL, method_unset_and_set_environment, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("ListUnitFiles", NULL, "a(ss)", method_list_unit_files, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("ListUnitFilesByPatterns", "asas", "a(ss)", method_list_unit_files_by_patterns, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("GetUnitFileState", "s", "s", method_get_unit_file_state, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("EnableUnitFiles", "asbb", "ba(sss)", method_enable_unit_files, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("DisableUnitFiles", "asb", "a(sss)", method_disable_unit_files, SD_BUS_VTABLE_UNPRIVILEGED), diff --git a/src/core/dbus-socket.c b/src/core/dbus-socket.c index d33e494f6b..bb09a515f8 100644 --- a/src/core/dbus-socket.c +++ b/src/core/dbus-socket.c @@ -149,6 +149,8 @@ const sd_bus_vtable bus_socket_vtable[] = { SD_BUS_PROPERTY("NAccepted", "u", bus_property_get_unsigned, offsetof(Socket, n_accepted), 0), SD_BUS_PROPERTY("FileDescriptorName", "s", property_get_fdname, 0, 0), SD_BUS_PROPERTY("SocketProtocol", "i", bus_property_get_int, offsetof(Socket, socket_protocol), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("TriggerLimitIntervalSec", "t", bus_property_get_usec, offsetof(Socket, trigger_limit.interval), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("TriggerLimitBurst", "u", bus_property_get_unsigned, offsetof(Socket, trigger_limit.burst), SD_BUS_VTABLE_PROPERTY_CONST), BUS_EXEC_COMMAND_LIST_VTABLE("ExecStartPre", offsetof(Socket, exec_command[SOCKET_EXEC_START_PRE]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), BUS_EXEC_COMMAND_LIST_VTABLE("ExecStartPost", offsetof(Socket, exec_command[SOCKET_EXEC_START_POST]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), BUS_EXEC_COMMAND_LIST_VTABLE("ExecStopPre", offsetof(Socket, exec_command[SOCKET_EXEC_STOP_PRE]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), diff --git a/src/core/dbus-unit.c b/src/core/dbus-unit.c index abe30413c3..e912fe2192 100644 --- a/src/core/dbus-unit.c +++ b/src/core/dbus-unit.c @@ -704,7 +704,8 @@ const sd_bus_vtable bus_unit_vtable[] = { SD_BUS_PROPERTY("Asserts", "a(sbbsi)", property_get_conditions, offsetof(Unit, asserts), 0), SD_BUS_PROPERTY("LoadError", "(ss)", property_get_load_error, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("Transient", "b", bus_property_get_bool, offsetof(Unit, transient), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("StartLimitInterval", "t", bus_property_get_usec, offsetof(Unit, start_limit.interval), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("StartLimitIntervalSec", "t", bus_property_get_usec, offsetof(Unit, start_limit.interval), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("StartLimitInterval", "t", bus_property_get_usec, offsetof(Unit, start_limit.interval), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN), /* obsolete alias name */ SD_BUS_PROPERTY("StartLimitBurst", "u", bus_property_get_unsigned, offsetof(Unit, start_limit.burst), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("StartLimitAction", "s", property_get_failure_action, offsetof(Unit, start_limit_action), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("RebootArgument", "s", NULL, offsetof(Unit, reboot_arg), SD_BUS_VTABLE_PROPERTY_CONST), diff --git a/src/core/device.c b/src/core/device.c index d01bec53d8..16e56efcc3 100644 --- a/src/core/device.c +++ b/src/core/device.c @@ -841,8 +841,6 @@ const UnitVTable device_vtable = { "Device\0" "Install\0", - .no_instances = true, - .init = device_init, .done = device_done, .load = unit_load_fragment_and_dropin_optional, diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4 index 5568b4696f..928b913c7b 100644 --- a/src/core/load-fragment-gperf.gperf.m4 +++ b/src/core/load-fragment-gperf.gperf.m4 @@ -164,6 +164,8 @@ Unit.IgnoreOnSnapshot, config_parse_warn_compat, DISABLED_LE Unit.JobTimeoutSec, config_parse_sec_fix_0, 0, offsetof(Unit, job_timeout) Unit.JobTimeoutAction, config_parse_failure_action, 0, offsetof(Unit, job_timeout_action) Unit.JobTimeoutRebootArgument, config_parse_string, 0, offsetof(Unit, job_timeout_reboot_arg) +Unit.StartLimitIntervalSec, config_parse_sec, 0, offsetof(Unit, start_limit.interval) +m4_dnl The following is a legacy alias name for compatibility Unit.StartLimitInterval, config_parse_sec, 0, offsetof(Unit, start_limit.interval) Unit.StartLimitBurst, config_parse_unsigned, 0, offsetof(Unit, start_limit.burst) Unit.StartLimitAction, config_parse_failure_action, 0, offsetof(Unit, start_limit_action) @@ -220,6 +222,7 @@ Service.TimeoutStartSec, config_parse_service_timeout, 0, Service.TimeoutStopSec, config_parse_service_timeout, 0, 0 Service.RuntimeMaxSec, config_parse_sec, 0, offsetof(Service, runtime_max_usec) Service.WatchdogSec, config_parse_sec, 0, offsetof(Service, watchdog_usec) +m4_dnl The following three only exist for compatibility, they moved into Unit, see above Service.StartLimitInterval, config_parse_sec, 0, offsetof(Unit, start_limit.interval) Service.StartLimitBurst, config_parse_unsigned, 0, offsetof(Unit, start_limit.burst) Service.StartLimitAction, config_parse_failure_action, 0, offsetof(Unit, start_limit_action) @@ -297,6 +300,8 @@ Socket.RemoveOnStop, config_parse_bool, 0, Socket.Symlinks, config_parse_unit_path_strv_printf, 0, offsetof(Socket, symlinks) Socket.FileDescriptorName, config_parse_fdname, 0, 0 Socket.Service, config_parse_socket_service, 0, 0 +Socket.TriggerLimitIntervalSec, config_parse_sec, 0, offsetof(Socket, trigger_limit.interval) +Socket.TriggerLimitBurst, config_parse_unsigned, 0, offsetof(Socket, trigger_limit.burst) m4_ifdef(`HAVE_SMACK', `Socket.SmackLabel, config_parse_string, 0, offsetof(Socket, smack) Socket.SmackLabelIPIn, config_parse_string, 0, offsetof(Socket, smack_ip_in) diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index c4566f7709..1a8c03904c 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -3427,10 +3427,10 @@ int config_parse_protect_system( #define FOLLOW_MAX 8 static int open_follow(char **filename, FILE **_f, Set *names, char **_final) { + char *id = NULL; unsigned c = 0; int fd, r; FILE *f; - char *id = NULL; assert(filename); assert(*filename); @@ -3452,7 +3452,6 @@ static int open_follow(char **filename, FILE **_f, Set *names, char **_final) { * the names of this unit, but only if it is a valid * unit name. */ name = basename(*filename); - if (unit_name_is_valid(name, UNIT_NAME_ANY)) { id = set_get(names, name); @@ -3492,6 +3491,7 @@ static int open_follow(char **filename, FILE **_f, Set *names, char **_final) { *_f = f; *_final = id; + return 0; } @@ -3552,13 +3552,13 @@ static int merge_by_names(Unit **u, Set *names, const char *id) { } static int load_from_path(Unit *u, const char *path) { - int r; _cleanup_set_free_free_ Set *symlink_names = NULL; _cleanup_fclose_ FILE *f = NULL; _cleanup_free_ char *filename = NULL; char *id = NULL; Unit *merged; struct stat st; + int r; assert(u); assert(path); @@ -3597,18 +3597,14 @@ static int load_from_path(Unit *u, const char *path) { r = -ENOENT; else r = open_follow(&filename, &f, symlink_names, &id); + if (r >= 0) + break; + filename = mfree(filename); + if (r != -ENOENT) + return r; - if (r < 0) { - filename = mfree(filename); - if (r != -ENOENT) - return r; - - /* Empty the symlink names for the next run */ - set_clear_free(symlink_names); - continue; - } - - break; + /* Empty the symlink names for the next run */ + set_clear_free(symlink_names); } } @@ -3616,6 +3612,11 @@ static int load_from_path(Unit *u, const char *path) { /* Hmm, no suitable file found? */ return 0; + if (!unit_type_may_alias(u->type) && set_size(symlink_names) > 1) { + log_unit_warning(u, "Unit type of %s does not support alias names, refusing loading via symlink.", u->id); + return -ELOOP; + } + merged = u; r = merge_by_names(&merged, symlink_names, id); if (r < 0) diff --git a/src/core/main.c b/src/core/main.c index 8dfb3928de..ed4d42c8cc 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -289,6 +289,7 @@ static int parse_crash_chvt(const char *value) { } static int set_machine_id(const char *m) { + assert(m); if (sd_id128_from_string(m, &arg_machine_id) < 0) return -EINVAL; @@ -413,6 +414,15 @@ static int parse_proc_cmdline_item(const char *key, const char *value) { target = runlevel_to_target(key); if (target) return free_and_strdup(&arg_default_unit, target); + + } else if (streq(key, "systemd.default_timeout_start_sec") && value) { + + r = parse_sec(value, &arg_default_timeout_start_usec); + if (r < 0) + log_warning_errno(r, "Failed to parse default start timeout: %s, ignoring.", value); + + if (arg_default_timeout_start_usec <= 0) + arg_default_timeout_start_usec = USEC_INFINITY; } return 0; @@ -660,7 +670,8 @@ static int parse_config_file(void) { { "Manager", "DefaultTimeoutStartSec", config_parse_sec, 0, &arg_default_timeout_start_usec }, { "Manager", "DefaultTimeoutStopSec", config_parse_sec, 0, &arg_default_timeout_stop_usec }, { "Manager", "DefaultRestartSec", config_parse_sec, 0, &arg_default_restart_usec }, - { "Manager", "DefaultStartLimitInterval", config_parse_sec, 0, &arg_default_start_limit_interval }, + { "Manager", "DefaultStartLimitInterval", config_parse_sec, 0, &arg_default_start_limit_interval }, /* obsolete alias */ + { "Manager", "DefaultStartLimitIntervalSec",config_parse_sec, 0, &arg_default_start_limit_interval }, { "Manager", "DefaultStartLimitBurst", config_parse_unsigned, 0, &arg_default_start_limit_burst }, { "Manager", "DefaultEnvironment", config_parse_environ, 0, &arg_default_environment }, { "Manager", "DefaultLimitCPU", config_parse_limit, RLIMIT_CPU, arg_default_rlimit }, diff --git a/src/core/mount.c b/src/core/mount.c index 188fb0aa40..c8a898e4dc 100644 --- a/src/core/mount.c +++ b/src/core/mount.c @@ -86,6 +86,15 @@ static bool mount_is_network(const MountParameters *p) { return mount_needs_network(p->options, p->fstype); } +static bool mount_is_loop(const MountParameters *p) { + assert(p); + + if (fstab_test_option(p->options, "loop\0")) + return true; + + return false; +} + static bool mount_is_bind(const MountParameters *p) { assert(p); @@ -269,12 +278,12 @@ static int mount_add_mount_links(Mount *m) { } /* Adds in links to other mount points that might be needed - * for the source path (if this is a bind mount) to be + * for the source path (if this is a bind mount or a loop mount) to be * available. */ pm = get_mount_parameters_fragment(m); if (pm && pm->what && path_is_absolute(pm->what) && - !mount_is_network(pm)) { + (mount_is_bind(pm) || mount_is_loop(pm) || !mount_is_network(pm))) { r = unit_require_mounts_for(UNIT(m), pm->what); if (r < 0) @@ -1830,9 +1839,6 @@ const UnitVTable mount_vtable = { "Install\0", .private_section = "Mount", - .no_alias = true, - .no_instances = true, - .init = mount_init, .load = mount_load, .done = mount_done, diff --git a/src/core/org.freedesktop.systemd1.conf b/src/core/org.freedesktop.systemd1.conf index b732501364..6c504a5e69 100644 --- a/src/core/org.freedesktop.systemd1.conf +++ b/src/core/org.freedesktop.systemd1.conf @@ -70,10 +70,18 @@ <allow send_destination="org.freedesktop.systemd1" send_interface="org.freedesktop.systemd1.Manager" + send_member="ListUnitsByPatterns"/> + + <allow send_destination="org.freedesktop.systemd1" + send_interface="org.freedesktop.systemd1.Manager" send_member="ListUnitFiles"/> <allow send_destination="org.freedesktop.systemd1" send_interface="org.freedesktop.systemd1.Manager" + send_member="ListUnitFilesByPatterns"/> + + <allow send_destination="org.freedesktop.systemd1" + send_interface="org.freedesktop.systemd1.Manager" send_member="GetUnitFileState"/> <allow send_destination="org.freedesktop.systemd1" diff --git a/src/core/scope.c b/src/core/scope.c index 7078d1f7e9..238f63a729 100644 --- a/src/core/scope.c +++ b/src/core/scope.c @@ -569,8 +569,6 @@ const UnitVTable scope_vtable = { "Install\0", .private_section = "Scope", - .no_alias = true, - .no_instances = true, .can_transient = true, .init = scope_init, diff --git a/src/core/service.c b/src/core/service.c index b46dd8bcdd..f7a3fcf2b9 100644 --- a/src/core/service.c +++ b/src/core/service.c @@ -180,20 +180,17 @@ static int service_set_main_pid(Service *s, pid_t pid) { return 0; } -static void service_close_socket_fd(Service *s) { +void service_close_socket_fd(Service *s) { assert(s); - s->socket_fd = asynchronous_close(s->socket_fd); -} - -static void service_connection_unref(Service *s) { - assert(s); + /* Undo the effect of service_set_socket_fd(). */ - if (!UNIT_ISSET(s->accept_socket)) - return; + s->socket_fd = asynchronous_close(s->socket_fd); - socket_connection_unref(SOCKET(UNIT_DEREF(s->accept_socket))); - unit_ref_unset(&s->accept_socket); + if (UNIT_ISSET(s->accept_socket)) { + socket_connection_unref(SOCKET(UNIT_DEREF(s->accept_socket))); + unit_ref_unset(&s->accept_socket); + } } static void service_stop_watchdog(Service *s) { @@ -321,7 +318,6 @@ static void service_done(Unit *u) { s->bus_name_owner = mfree(s->bus_name_owner); service_close_socket_fd(s); - service_connection_unref(s); unit_ref_unset(&s->accept_socket); @@ -910,10 +906,8 @@ static void service_set_state(Service *s, ServiceState state) { SERVICE_RUNNING, SERVICE_RELOAD, SERVICE_STOP, SERVICE_STOP_SIGABRT, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST, SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL) && - !(state == SERVICE_DEAD && UNIT(s)->job)) { + !(state == SERVICE_DEAD && UNIT(s)->job)) service_close_socket_fd(s); - service_connection_unref(s); - } if (!IN_SET(state, SERVICE_START_POST, SERVICE_RUNNING, SERVICE_RELOAD)) service_stop_watchdog(s); @@ -3139,9 +3133,8 @@ int service_set_socket_fd(Service *s, int fd, Socket *sock, bool selinux_context assert(s); assert(fd >= 0); - /* This is called by the socket code when instantiating a new - * service for a stream socket and the socket needs to be - * configured. */ + /* This is called by the socket code when instantiating a new service for a stream socket and the socket needs + * to be configured. We take ownership of the passed fd on success. */ if (UNIT(s)->load_state != UNIT_LOADED) return -EINVAL; @@ -3169,12 +3162,15 @@ int service_set_socket_fd(Service *s, int fd, Socket *sock, bool selinux_context return r; } + r = unit_add_two_dependencies(UNIT(sock), UNIT_BEFORE, UNIT_TRIGGERS, UNIT(s), false); + if (r < 0) + return r; + s->socket_fd = fd; s->socket_fd_selinux_context_net = selinux_context_net; unit_ref_set(&s->accept_socket, UNIT(sock)); - - return unit_add_two_dependencies(UNIT(sock), UNIT_BEFORE, UNIT_TRIGGERS, UNIT(s), false); + return 0; } static void service_reset_failed(Unit *u) { diff --git a/src/core/service.h b/src/core/service.h index a5ced215e4..c7f1e81bdb 100644 --- a/src/core/service.h +++ b/src/core/service.h @@ -80,7 +80,7 @@ typedef enum NotifyState { typedef enum ServiceResult { SERVICE_SUCCESS, - SERVICE_FAILURE_RESOURCES, + SERVICE_FAILURE_RESOURCES, /* a bit of a misnomer, just our catch-all error for errnos we didn't expect */ SERVICE_FAILURE_TIMEOUT, SERVICE_FAILURE_EXIT_CODE, SERVICE_FAILURE_SIGNAL, @@ -198,6 +198,7 @@ struct Service { extern const UnitVTable service_vtable; int service_set_socket_fd(Service *s, int fd, struct Socket *socket, bool selinux_context_net); +void service_close_socket_fd(Service *s); const char* service_restart_to_string(ServiceRestart i) _const_; ServiceRestart service_restart_from_string(const char *s) _pure_; diff --git a/src/core/slice.c b/src/core/slice.c index 63a77c9bca..c7700b8857 100644 --- a/src/core/slice.c +++ b/src/core/slice.c @@ -309,8 +309,6 @@ const UnitVTable slice_vtable = { "Install\0", .private_section = "Slice", - .no_alias = true, - .no_instances = true, .can_transient = true, .init = slice_init, diff --git a/src/core/socket.c b/src/core/socket.c index a9fff9c259..7eeed068bd 100644 --- a/src/core/socket.c +++ b/src/core/socket.c @@ -99,6 +99,8 @@ static void socket_init(Unit *u) { s->exec_context.std_error = u->manager->default_std_error; s->control_command_id = _SOCKET_EXEC_COMMAND_INVALID; + + RATELIMIT_INIT(s->trigger_limit, 5*USEC_PER_SEC, 2500); } static void socket_unwatch_control_pid(Socket *s) { @@ -227,7 +229,6 @@ int socket_instantiate_service(Socket *s) { if (r < 0) return r; - u->no_gc = true; unit_ref_set(&s->service, u); return unit_add_two_dependencies(UNIT(s), UNIT_BEFORE, UNIT_TRIGGERS, u, false); @@ -792,47 +793,45 @@ static void socket_close_fds(Socket *s) { assert(s); LIST_FOREACH(port, p, s->ports) { + bool was_open; - p->event_source = sd_event_source_unref(p->event_source); - - if (p->fd < 0) - continue; + was_open = p->fd >= 0; + p->event_source = sd_event_source_unref(p->event_source); p->fd = safe_close(p->fd); socket_cleanup_fd_list(p); - /* One little note: we should normally not delete any - * sockets in the file system here! After all some - * other process we spawned might still have a - * reference of this fd and wants to continue to use - * it. Therefore we delete sockets in the file system - * before we create a new one, not after we stopped - * using one! */ + /* One little note: we should normally not delete any sockets in the file system here! After all some + * other process we spawned might still have a reference of this fd and wants to continue to use + * it. Therefore we normally delete sockets in the file system before we create a new one, not after we + * stopped using one! That all said, if the user explicitly requested this, we'll delete them here + * anyway, but only then. */ - if (s->remove_on_stop) { - switch (p->type) { + if (!was_open || !s->remove_on_stop) + continue; - case SOCKET_FIFO: - unlink(p->path); - break; + switch (p->type) { - case SOCKET_MQUEUE: - mq_unlink(p->path); - break; + case SOCKET_FIFO: + (void) unlink(p->path); + break; - case SOCKET_SOCKET: - socket_address_unlink(&p->address); - break; + case SOCKET_MQUEUE: + (void) mq_unlink(p->path); + break; - default: - break; - } + case SOCKET_SOCKET: + (void) socket_address_unlink(&p->address); + break; + + default: + break; } } if (s->remove_on_stop) STRV_FOREACH(i, s->symlinks) - unlink(*i); + (void) unlink(*i); } static void socket_apply_socket_options(Socket *s, int fd) { @@ -1887,6 +1886,9 @@ static void socket_enter_running(Socket *s, int cfd) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; int r; + /* Note that this call takes possession of the connection fd passed. It either has to assign it somewhere or + * close it. */ + assert(s); /* We don't take connections anymore if we are supposed to @@ -1896,7 +1898,7 @@ static void socket_enter_running(Socket *s, int cfd) { log_unit_debug(UNIT(s), "Suppressing connection request since unit stop is scheduled."); if (cfd >= 0) - safe_close(cfd); + cfd = safe_close(cfd); else { /* Flush all sockets by closing and reopening them */ socket_close_fds(s); @@ -1918,6 +1920,13 @@ static void socket_enter_running(Socket *s, int cfd) { return; } + if (!ratelimit_test(&s->trigger_limit)) { + safe_close(cfd); + log_unit_warning(UNIT(s), "Trigger limit hit, refusing further activation."); + socket_enter_stop_pre(s, SOCKET_FAILURE_TRIGGER_LIMIT_HIT); + return; + } + if (cfd < 0) { Iterator i; Unit *other; @@ -1949,7 +1958,7 @@ static void socket_enter_running(Socket *s, int cfd) { Service *service; if (s->n_connections >= s->max_connections) { - log_unit_warning(UNIT(s), "Too many incoming connections (%u)", s->n_connections); + log_unit_warning(UNIT(s), "Too many incoming connections (%u), refusing connection attempt.", s->n_connections); safe_close(cfd); return; } @@ -1965,6 +1974,7 @@ static void socket_enter_running(Socket *s, int cfd) { /* ENOTCONN is legitimate if TCP RST was received. * This connection is over, but the socket unit lives on. */ + log_unit_debug(UNIT(s), "Got ENOTCONN on incoming socket, assuming aborted connection attempt, ignoring."); safe_close(cfd); return; } @@ -1983,22 +1993,24 @@ static void socket_enter_running(Socket *s, int cfd) { service = SERVICE(UNIT_DEREF(s->service)); unit_ref_unset(&s->service); - s->n_accepted++; - - UNIT(service)->no_gc = false; + s->n_accepted++; unit_choose_id(UNIT(service), name); r = service_set_socket_fd(service, cfd, s, s->selinux_context_from_net); if (r < 0) goto fail; - cfd = -1; + cfd = -1; /* We passed ownership of the fd to the service now. Forget it here. */ s->n_connections++; r = manager_add_job(UNIT(s)->manager, JOB_START, UNIT(service), JOB_REPLACE, &error, NULL); - if (r < 0) + if (r < 0) { + /* We failed to activate the new service, but it still exists. Let's make sure the service + * closes and forgets the connection fd again, immediately. */ + service_close_socket_fd(service); goto fail; + } /* Notify clients about changed counters */ unit_add_to_dbus_queue(UNIT(s)); @@ -2806,6 +2818,7 @@ static const char* const socket_result_table[_SOCKET_RESULT_MAX] = { [SOCKET_FAILURE_EXIT_CODE] = "exit-code", [SOCKET_FAILURE_SIGNAL] = "signal", [SOCKET_FAILURE_CORE_DUMP] = "core-dump", + [SOCKET_FAILURE_TRIGGER_LIMIT_HIT] = "trigger-limit-hit", [SOCKET_FAILURE_SERVICE_START_LIMIT_HIT] = "service-start-limit-hit" }; diff --git a/src/core/socket.h b/src/core/socket.h index b537b026a7..2a4b1bb674 100644 --- a/src/core/socket.h +++ b/src/core/socket.h @@ -52,6 +52,7 @@ typedef enum SocketResult { SOCKET_FAILURE_EXIT_CODE, SOCKET_FAILURE_SIGNAL, SOCKET_FAILURE_CORE_DUMP, + SOCKET_FAILURE_TRIGGER_LIMIT_HIT, SOCKET_FAILURE_SERVICE_START_LIMIT_HIT, _SOCKET_RESULT_MAX, _SOCKET_RESULT_INVALID = -1 @@ -156,6 +157,8 @@ struct Socket { bool reset_cpu_usage:1; char *fdname; + + RateLimit trigger_limit; }; /* Called from the service code when collecting fds */ diff --git a/src/core/swap.c b/src/core/swap.c index d8802470d2..c018648d87 100644 --- a/src/core/swap.c +++ b/src/core/swap.c @@ -1465,9 +1465,6 @@ const UnitVTable swap_vtable = { "Install\0", .private_section = "Swap", - .no_alias = true, - .no_instances = true, - .init = swap_init, .load = swap_load, .done = swap_done, diff --git a/src/core/system.conf b/src/core/system.conf index e2ded27333..eacd7ee282 100644 --- a/src/core/system.conf +++ b/src/core/system.conf @@ -34,7 +34,7 @@ #DefaultTimeoutStartSec=90s #DefaultTimeoutStopSec=90s #DefaultRestartSec=100ms -#DefaultStartLimitInterval=10s +#DefaultStartLimitIntervalSec=10s #DefaultStartLimitBurst=5 #DefaultEnvironment= #DefaultCPUAccounting=no diff --git a/src/core/unit.c b/src/core/unit.c index cb79c7c6b1..4ace6b075b 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -193,7 +193,7 @@ int unit_add_name(Unit *u, const char *text) { if (r < 0) return r; - if (i && unit_vtable[t]->no_instances) + if (i && !unit_type_may_template(t)) return -EINVAL; /* Ensure that this unit is either instanced or not instanced, @@ -202,7 +202,7 @@ int unit_add_name(Unit *u, const char *text) { if (u->type != _UNIT_TYPE_INVALID && !u->instance != !i) return -EINVAL; - if (unit_vtable[t]->no_alias && !set_isempty(u->names)) + if (!unit_type_may_alias(t) && !set_isempty(u->names)) return -EEXIST; if (hashmap_size(u->manager->units) >= MANAGER_MAX_NAMES) @@ -720,6 +720,9 @@ int unit_merge(Unit *u, Unit *other) { if (!u->instance != !other->instance) return -EINVAL; + if (!unit_type_may_alias(u->type)) /* Merging only applies to unit names that support aliases */ + return -EEXIST; + if (other->load_state != UNIT_STUB && other->load_state != UNIT_NOT_FOUND) return -EEXIST; @@ -776,9 +779,9 @@ int unit_merge(Unit *u, Unit *other) { } int unit_merge_by_name(Unit *u, const char *name) { + _cleanup_free_ char *s = NULL; Unit *other; int r; - _cleanup_free_ char *s = NULL; assert(u); assert(name); @@ -1497,11 +1500,6 @@ int unit_start(Unit *u) { if (UNIT_IS_ACTIVE_OR_RELOADING(state)) return -EALREADY; - /* Make sure we don't enter a busy loop of some kind. */ - r = unit_start_limit_test(u); - if (r < 0) - return r; - /* Units that aren't loaded cannot be started */ if (u->load_state != UNIT_LOADED) return -EINVAL; @@ -1543,6 +1541,11 @@ int unit_start(Unit *u) { if (!UNIT_VTABLE(u)->start) return -EBADR; + /* Make sure we don't enter a busy loop of some kind. */ + r = unit_start_limit_test(u); + if (r < 0) + return r; + /* We don't suppress calls to ->start() here when we are * already starting, to allow this request to be used as a * "hurry up" call, for example when the unit is in some "auto @@ -3222,6 +3225,10 @@ void unit_ref_unset(UnitRef *ref) { if (!ref->unit) return; + /* We are about to drop a reference to the unit, make sure the garbage collection has a look at it as it might + * be unreferenced now. */ + unit_add_to_gc_queue(ref->unit); + LIST_REMOVE(refs, ref->unit->refs, ref); ref->unit = NULL; } diff --git a/src/core/unit.h b/src/core/unit.h index 5909652976..be62e88421 100644 --- a/src/core/unit.h +++ b/src/core/unit.h @@ -416,12 +416,6 @@ struct UnitVTable { /* The strings to print in status messages */ UnitStatusMessageFormats status_message_formats; - /* Can units of this type have multiple names? */ - bool no_alias:1; - - /* Instances make no sense for this type */ - bool no_instances:1; - /* True if transient units of this type are OK */ bool can_transient:1; }; diff --git a/src/core/user.conf b/src/core/user.conf index 87c8164378..b427f1ef6d 100644 --- a/src/core/user.conf +++ b/src/core/user.conf @@ -23,7 +23,7 @@ #DefaultTimeoutStartSec=90s #DefaultTimeoutStopSec=90s #DefaultRestartSec=100ms -#DefaultStartLimitInterval=10s +#DefaultStartLimitIntervalSec=10s #DefaultStartLimitBurst=5 #DefaultEnvironment= #DefaultLimitCPU= diff --git a/src/import/pull-common.c b/src/import/pull-common.c index d301d4d79e..dc4e4667a9 100644 --- a/src/import/pull-common.c +++ b/src/import/pull-common.c @@ -330,7 +330,7 @@ int pull_verify(PullJob *main_job, _cleanup_close_ int sig_file = -1; const char *p, *line; char sig_file_path[] = "/tmp/sigXXXXXX", gpg_home[] = "/tmp/gpghomeXXXXXX"; - _cleanup_sigkill_wait_ pid_t pid = 0; + _cleanup_(sigkill_waitp) pid_t pid = 0; bool gpg_home_created = false; int r; diff --git a/src/journal/journal-file.c b/src/journal/journal-file.c index bed825cdc3..ec50333c2c 100644 --- a/src/journal/journal-file.c +++ b/src/journal/journal-file.c @@ -37,6 +37,7 @@ #include "journal-file.h" #include "lookup3.h" #include "parse-util.h" +#include "path-util.h" #include "random-util.h" #include "sd-event.h" #include "set.h" @@ -119,7 +120,7 @@ static void journal_file_set_offline_internal(JournalFile *f) { if (!__sync_bool_compare_and_swap(&f->offline_state, OFFLINE_SYNCING, OFFLINE_OFFLINING)) continue; - f->header->state = STATE_OFFLINE; + f->header->state = f->archive ? STATE_ARCHIVED : STATE_OFFLINE; (void) fsync(f->fd); break; @@ -217,8 +218,10 @@ int journal_file_set_offline(JournalFile *f, bool wait) { if (!(f->fd >= 0 && f->header)) return -EINVAL; - if (f->header->state != STATE_ONLINE) - return 0; + /* An offlining journal is implicitly online and may modify f->header->state, + * we must also join any potentially lingering offline thread when not online. */ + if (!journal_file_is_offlining(f) && f->header->state != STATE_ONLINE) + return journal_file_set_offline_thread_join(f); /* Restart an in-flight offline thread and wait if needed, or join a lingering done one. */ restarted = journal_file_set_offline_try_restart(f); @@ -362,7 +365,8 @@ JournalFile* journal_file_close(JournalFile *f) { (void) btrfs_defrag_fd(f->fd); } - safe_close(f->fd); + if (f->close_fd) + safe_close(f->fd); free(f->path); mmap_cache_unref(f->mmap); @@ -435,6 +439,39 @@ static int journal_file_init_header(JournalFile *f, JournalFile *template) { return 0; } +static int fsync_directory_of_file(int fd) { + _cleanup_free_ char *path = NULL, *dn = NULL; + _cleanup_close_ int dfd = -1; + struct stat st; + int r; + + if (fstat(fd, &st) < 0) + return -errno; + + if (!S_ISREG(st.st_mode)) + return -EBADFD; + + r = fd_get_path(fd, &path); + if (r < 0) + return r; + + if (!path_is_absolute(path)) + return -EINVAL; + + dn = dirname_malloc(path); + if (!dn) + return -ENOMEM; + + dfd = open(dn, O_RDONLY|O_CLOEXEC|O_DIRECTORY); + if (dfd < 0) + return -errno; + + if (fsync(dfd) < 0) + return -errno; + + return 0; +} + static int journal_file_refresh_header(JournalFile *f) { sd_id128_t boot_id; int r; @@ -460,6 +497,9 @@ static int journal_file_refresh_header(JournalFile *f) { /* Sync the online state to disk */ (void) fsync(f->fd); + /* We likely just created a new file, also sync the directory this file is located in. */ + (void) fsync_directory_of_file(f->fd); + return r; } @@ -703,7 +743,11 @@ int journal_file_move_to_object(JournalFile *f, ObjectType type, uint64_t offset /* Objects may only be located at multiple of 64 bit */ if (!VALID64(offset)) - return -EFAULT; + return -EBADMSG; + + /* Object may not be located in the file header */ + if (offset < le64toh(f->header->header_size)) + return -EBADMSG; r = journal_file_move_to(f, type, false, offset, sizeof(ObjectHeader), &t); if (r < 0) @@ -1976,9 +2020,14 @@ static int generic_array_bisect( i = right - 1; lp = p = le64toh(array->entry_array.items[i]); if (p <= 0) - return -EBADMSG; - - r = test_object(f, p, needle); + r = -EBADMSG; + else + r = test_object(f, p, needle); + if (r == -EBADMSG) { + log_debug_errno(r, "Encountered invalid entry while bisecting, cutting algorithm short. (1)"); + n = i; + continue; + } if (r < 0) return r; @@ -2054,9 +2103,14 @@ static int generic_array_bisect( p = le64toh(array->entry_array.items[i]); if (p <= 0) - return -EBADMSG; - - r = test_object(f, p, needle); + r = -EBADMSG; + else + r = test_object(f, p, needle); + if (r == -EBADMSG) { + log_debug_errno(r, "Encountered invalid entry while bisecting, cutting algorithm short. (2)"); + right = n = i; + continue; + } if (r < 0) return r; @@ -2461,13 +2515,18 @@ int journal_file_next_entry( le64toh(f->header->entry_array_offset), i, ret, &ofs); + if (r == -EBADMSG && direction == DIRECTION_DOWN) { + /* Special case: when we iterate throught the journal file linearly, and hit an entry we can't read, + * consider this the end of the journal file. */ + log_debug_errno(r, "Encountered entry we can't read while iterating through journal file. Considering this the end of the file."); + return 0; + } if (r <= 0) return r; if (p > 0 && (direction == DIRECTION_DOWN ? ofs <= p : ofs >= p)) { - log_debug("%s: entry array corrupted at entry %"PRIu64, - f->path, i); + log_debug("%s: entry array corrupted at entry %" PRIu64, f->path, i); return -EBADMSG; } @@ -2806,11 +2865,11 @@ void journal_file_print_header(JournalFile *f) { "Data Hash Table Size: %"PRIu64"\n" "Field Hash Table Size: %"PRIu64"\n" "Rotate Suggested: %s\n" - "Head Sequential Number: %"PRIu64"\n" - "Tail Sequential Number: %"PRIu64"\n" - "Head Realtime Timestamp: %s\n" - "Tail Realtime Timestamp: %s\n" - "Tail Monotonic Timestamp: %s\n" + "Head Sequential Number: %"PRIu64" (%"PRIx64")\n" + "Tail Sequential Number: %"PRIu64" (%"PRIx64")\n" + "Head Realtime Timestamp: %s (%"PRIx64")\n" + "Tail Realtime Timestamp: %s (%"PRIx64")\n" + "Tail Monotonic Timestamp: %s (%"PRIx64")\n" "Objects: %"PRIu64"\n" "Entry Objects: %"PRIu64"\n", f->path, @@ -2831,11 +2890,11 @@ void journal_file_print_header(JournalFile *f) { le64toh(f->header->data_hash_table_size) / sizeof(HashItem), le64toh(f->header->field_hash_table_size) / sizeof(HashItem), yes_no(journal_file_rotate_suggested(f, 0)), - le64toh(f->header->head_entry_seqnum), - le64toh(f->header->tail_entry_seqnum), - format_timestamp_safe(x, sizeof(x), le64toh(f->header->head_entry_realtime)), - format_timestamp_safe(y, sizeof(y), le64toh(f->header->tail_entry_realtime)), - format_timespan(z, sizeof(z), le64toh(f->header->tail_entry_monotonic), USEC_PER_MSEC), + le64toh(f->header->head_entry_seqnum), le64toh(f->header->head_entry_seqnum), + le64toh(f->header->tail_entry_seqnum), le64toh(f->header->tail_entry_seqnum), + format_timestamp_safe(x, sizeof(x), le64toh(f->header->head_entry_realtime)), le64toh(f->header->head_entry_realtime), + format_timestamp_safe(y, sizeof(y), le64toh(f->header->tail_entry_realtime)), le64toh(f->header->tail_entry_realtime), + format_timespan(z, sizeof(z), le64toh(f->header->tail_entry_monotonic), USEC_PER_MSEC), le64toh(f->header->tail_entry_monotonic), le64toh(f->header->n_objects), le64toh(f->header->n_entries)); @@ -2898,6 +2957,7 @@ static int journal_file_warn_btrfs(JournalFile *f) { } int journal_file_open( + int fd, const char *fname, int flags, mode_t mode, @@ -2914,22 +2974,24 @@ int journal_file_open( void *h; int r; - assert(fname); assert(ret); + assert(fd >= 0 || fname); if ((flags & O_ACCMODE) != O_RDONLY && (flags & O_ACCMODE) != O_RDWR) return -EINVAL; - if (!endswith(fname, ".journal") && - !endswith(fname, ".journal~")) - return -EINVAL; + if (fname) { + if (!endswith(fname, ".journal") && + !endswith(fname, ".journal~")) + return -EINVAL; + } f = new0(JournalFile, 1); if (!f) return -ENOMEM; - f->fd = -1; + f->fd = fd; f->mode = mode; f->flags = flags; @@ -2954,7 +3016,10 @@ int journal_file_open( } } - f->path = strdup(fname); + if (fname) + f->path = strdup(fname); + else /* If we don't know the path, fill in something explanatory and vaguely useful */ + asprintf(&f->path, "/proc/self/%i", fd); if (!f->path) { r = -ENOMEM; goto fail; @@ -2966,10 +3031,15 @@ int journal_file_open( goto fail; } - f->fd = open(f->path, f->flags|O_CLOEXEC, f->mode); if (f->fd < 0) { - r = -errno; - goto fail; + f->fd = open(f->path, f->flags|O_CLOEXEC, f->mode); + if (f->fd < 0) { + r = -errno; + goto fail; + } + + /* fds we opened here by us should also be closed by us. */ + f->close_fd = true; } r = journal_file_fstat(f); @@ -3090,6 +3160,9 @@ int journal_file_open( goto fail; } + /* The file is opened now successfully, thus we take possesion of any passed in fd. */ + f->close_fd = true; + *ret = f; return 0; @@ -3116,6 +3189,11 @@ int journal_file_rotate(JournalFile **f, bool compress, bool seal, Set *deferred if (!old_file->writable) return -EINVAL; + /* Is this a journal file that was passed to us as fd? If so, we synthesized a path name for it, and we refuse + * rotation, since we don't know the actual path, and couldn't rename the file hence.*/ + if (path_startswith(old_file->path, "/proc/self/fd")) + return -EINVAL; + if (!endswith(old_file->path, ".journal")) return -EINVAL; @@ -3135,14 +3213,23 @@ int journal_file_rotate(JournalFile **f, bool compress, bool seal, Set *deferred if (r < 0 && errno != ENOENT) return -errno; - old_file->header->state = STATE_ARCHIVED; + /* Sync the rename to disk */ + (void) fsync_directory_of_file(old_file->fd); + + /* Set as archive so offlining commits w/state=STATE_ARCHIVED. + * Previously we would set old_file->header->state to STATE_ARCHIVED directly here, + * but journal_file_set_offline() short-circuits when state != STATE_ONLINE, which + * would result in the rotated journal never getting fsync() called before closing. + * Now we simply queue the archive state by setting an archive bit, leaving the state + * as STATE_ONLINE so proper offlining occurs. */ + old_file->archive = true; /* Currently, btrfs is not very good with out write patterns * and fragments heavily. Let's defrag our journal files when * we archive them */ old_file->defrag_on_close = true; - r = journal_file_open(old_file->path, old_file->flags, old_file->mode, compress, seal, NULL, old_file->mmap, deferred_closes, old_file, &new_file); + r = journal_file_open(-1, old_file->path, old_file->flags, old_file->mode, compress, seal, NULL, old_file->mmap, deferred_closes, old_file, &new_file); if (deferred_closes && set_put(deferred_closes, old_file) >= 0) @@ -3170,7 +3257,7 @@ int journal_file_open_reliably( size_t l; _cleanup_free_ char *p = NULL; - r = journal_file_open(fname, flags, mode, compress, seal, metrics, mmap_cache, deferred_closes, template, ret); + r = journal_file_open(-1, fname, flags, mode, compress, seal, metrics, mmap_cache, deferred_closes, template, ret); if (!IN_SET(r, -EBADMSG, /* corrupted */ -ENODATA, /* truncated */ @@ -3206,12 +3293,12 @@ int journal_file_open_reliably( /* btrfs doesn't cope well with our write pattern and * fragments heavily. Let's defrag all files we rotate */ - (void) chattr_path(p, false, FS_NOCOW_FL); + (void) chattr_path(p, 0, FS_NOCOW_FL); (void) btrfs_defrag(p); log_warning_errno(r, "File %s corrupted or uncleanly shut down, renaming and replacing.", fname); - return journal_file_open(fname, flags, mode, compress, seal, metrics, mmap_cache, deferred_closes, template, ret); + return journal_file_open(-1, fname, flags, mode, compress, seal, metrics, mmap_cache, deferred_closes, template, ret); } int journal_file_copy_entry(JournalFile *from, JournalFile *to, Object *o, uint64_t p, uint64_t *seqnum, Object **ret, uint64_t *offset) { diff --git a/src/journal/journal-file.h b/src/journal/journal-file.h index 9ad6013359..564e1a8179 100644 --- a/src/journal/journal-file.h +++ b/src/journal/journal-file.h @@ -85,6 +85,8 @@ typedef struct JournalFile { bool compress_lz4:1; bool seal:1; bool defrag_on_close:1; + bool close_fd:1; + bool archive:1; bool tail_entry_monotonic_valid:1; @@ -142,6 +144,7 @@ typedef struct JournalFile { } JournalFile; int journal_file_open( + int fd, const char *fname, int flags, mode_t mode, diff --git a/src/journal/journal-internal.h b/src/journal/journal-internal.h index 7639325acf..34a48141f5 100644 --- a/src/journal/journal-internal.h +++ b/src/journal/journal-internal.h @@ -82,6 +82,8 @@ struct Directory { }; struct sd_journal { + int toplevel_fd; + char *path; char *prefix; @@ -117,6 +119,7 @@ struct sd_journal { bool on_network:1; bool no_new_files:1; + bool no_inotify:1; bool unique_file_lost:1; /* File we were iterating over got removed, and there were no more files, so sd_j_enumerate_unique diff --git a/src/journal/journalctl.c b/src/journal/journalctl.c index c9a2c3812d..f67c556783 100644 --- a/src/journal/journalctl.c +++ b/src/journal/journalctl.c @@ -101,6 +101,7 @@ static const char *arg_after_cursor = NULL; static bool arg_show_cursor = false; static const char *arg_directory = NULL; static char **arg_file = NULL; +static bool arg_file_stdin = false; static int arg_priorities = 0xFF; static const char *arg_verify_key = NULL; #ifdef HAVE_GCRYPT @@ -592,9 +593,17 @@ static int parse_argv(int argc, char *argv[]) { break; case ARG_FILE: - r = glob_extend(&arg_file, optarg); - if (r < 0) - return log_error_errno(r, "Failed to add paths: %m"); + if (streq(optarg, "-")) + /* An undocumented feature: we can read journal files from STDIN. We don't document + * this though, since after all we only support this for mmap-able, seekable files, and + * not for example pipes which are probably the primary usecase for reading things from + * STDIN. To avoid confusion we hence don't document this feature. */ + arg_file_stdin = true; + else { + r = glob_extend(&arg_file, optarg); + if (r < 0) + return log_error_errno(r, "Failed to add paths: %m"); + } break; case ARG_ROOT: @@ -864,6 +873,18 @@ static int parse_argv(int argc, char *argv[]) { return -EINVAL; } + if (!strv_isempty(arg_system_units) && (arg_journal_type == SD_JOURNAL_CURRENT_USER)) { + + /* Specifying --user and --unit= at the same time makes no sense (as the former excludes the user + * journal, but the latter excludes the system journal, thus resulting in empty output). Let's be nice + * to users, and automatically turn --unit= into --user-unit= if combined with --user. */ + r = strv_extend_strv(&arg_user_units, arg_system_units, true); + if (r < 0) + return -ENOMEM; + + arg_system_units = strv_free(arg_system_units); + } + return 1; } @@ -988,18 +1009,18 @@ static void boot_id_free_all(BootId *l) { } } -static int discover_next_boot( - sd_journal *j, - BootId **boot, +static int discover_next_boot(sd_journal *j, + sd_id128_t previous_boot_id, bool advance_older, - bool read_realtime) { + BootId **ret) { - int r; - char match[9+32+1] = "_BOOT_ID="; _cleanup_free_ BootId *next_boot = NULL; + char match[9+32+1] = "_BOOT_ID="; + sd_id128_t boot_id; + int r; assert(j); - assert(boot); + assert(ret); /* We expect the journal to be on the last position of a boot * (in relation to the direction we are going), so that the next @@ -1012,29 +1033,40 @@ static int discover_next_boot( * we can actually advance to a *different* boot. */ sd_journal_flush_matches(j); - if (advance_older) - r = sd_journal_previous(j); - else - r = sd_journal_next(j); - if (r < 0) - return r; - else if (r == 0) - return 0; /* End of journal, yay. */ + do { + if (advance_older) + r = sd_journal_previous(j); + else + r = sd_journal_next(j); + if (r < 0) + return r; + else if (r == 0) + return 0; /* End of journal, yay. */ + + r = sd_journal_get_monotonic_usec(j, NULL, &boot_id); + if (r < 0) + return r; + + /* We iterate through this in a loop, until the boot ID differs from the previous one. Note that + * normally, this will only require a single iteration, as we seeked to the last entry of the previous + * boot entry already. However, it might happen that the per-journal-field entry arrays are less + * complete than the main entry array, and hence might reference an entry that's not actually the last + * one of the boot ID as last one. Let's hence use the per-field array is initial seek position to + * speed things up, but let's not trust that it is complete, and hence, manually advance as + * necessary. */ + + } while (sd_id128_equal(boot_id, previous_boot_id)); next_boot = new0(BootId, 1); if (!next_boot) return -ENOMEM; - r = sd_journal_get_monotonic_usec(j, NULL, &next_boot->id); + next_boot->id = boot_id; + + r = sd_journal_get_realtime_usec(j, &next_boot->first); if (r < 0) return r; - if (read_realtime) { - r = sd_journal_get_realtime_usec(j, &next_boot->first); - if (r < 0) - return r; - } - /* Now seek to the last occurrence of this boot ID. */ sd_id128_to_string(next_boot->id, match + 9); r = sd_journal_add_match(j, match, sizeof(match) - 1); @@ -1057,13 +1089,11 @@ static int discover_next_boot( else if (r == 0) return -ENODATA; /* This shouldn't happen. We just came from this very boot ID. */ - if (read_realtime) { - r = sd_journal_get_realtime_usec(j, &next_boot->last); - if (r < 0) - return r; - } + r = sd_journal_get_realtime_usec(j, &next_boot->last); + if (r < 0) + return r; - *boot = next_boot; + *ret = next_boot; next_boot = NULL; return 0; @@ -1072,47 +1102,48 @@ static int discover_next_boot( static int get_boots( sd_journal *j, BootId **boots, - BootId *query_ref_boot, + sd_id128_t *query_ref_boot, int ref_boot_offset) { bool skip_once; int r, count = 0; BootId *head = NULL, *tail = NULL; const bool advance_older = query_ref_boot && ref_boot_offset <= 0; + sd_id128_t previous_boot_id; assert(j); /* Adjust for the asymmetry that offset 0 is * the last (and current) boot, while 1 is considered the * (chronological) first boot in the journal. */ - skip_once = query_ref_boot && sd_id128_is_null(query_ref_boot->id) && ref_boot_offset < 0; + skip_once = query_ref_boot && sd_id128_is_null(*query_ref_boot) && ref_boot_offset < 0; /* Advance to the earliest/latest occurrence of our reference * boot ID (taking our lookup direction into account), so that * discover_next_boot() can do its job. * If no reference is given, the journal head/tail will do, * they're "virtual" boots after all. */ - if (query_ref_boot && !sd_id128_is_null(query_ref_boot->id)) { + if (query_ref_boot && !sd_id128_is_null(*query_ref_boot)) { char match[9+32+1] = "_BOOT_ID="; sd_journal_flush_matches(j); - sd_id128_to_string(query_ref_boot->id, match + 9); + sd_id128_to_string(*query_ref_boot, match + 9); r = sd_journal_add_match(j, match, sizeof(match) - 1); if (r < 0) return r; if (advance_older) - r = sd_journal_seek_head(j); + r = sd_journal_seek_head(j); /* seek to oldest */ else - r = sd_journal_seek_tail(j); + r = sd_journal_seek_tail(j); /* seek to newest */ if (r < 0) return r; if (advance_older) - r = sd_journal_next(j); + r = sd_journal_next(j); /* read the oldest entry */ else - r = sd_journal_previous(j); + r = sd_journal_previous(j); /* read the most recently added entry */ if (r < 0) return r; else if (r == 0) @@ -1121,21 +1152,31 @@ static int get_boots( count = 1; goto finish; } + + /* At this point the read pointer is positioned at the oldest/newest occurence of the reference boot + * ID. After flushing the matches, one more invocation of _previous()/_next() will hence place us at + * the following entry, which must then have an older/newer boot ID */ } else { + if (advance_older) - r = sd_journal_seek_tail(j); + r = sd_journal_seek_tail(j); /* seek to newest */ else - r = sd_journal_seek_head(j); + r = sd_journal_seek_head(j); /* seek to oldest */ if (r < 0) return r; - /* No sd_journal_next/previous here. */ + /* No sd_journal_next()/_previous() here. + * + * At this point the read pointer is positioned after the newest/before the oldest entry in the whole + * journal. The next invocation of _previous()/_next() will hence position us at the newest/oldest + * entry we have. */ } + previous_boot_id = SD_ID128_NULL; for (;;) { _cleanup_free_ BootId *current = NULL; - r = discover_next_boot(j, ¤t, advance_older, !query_ref_boot); + r = discover_next_boot(j, previous_boot_id, advance_older, ¤t); if (r < 0) { boot_id_free_all(head); return r; @@ -1144,6 +1185,8 @@ static int get_boots( if (!current) break; + previous_boot_id = current->id; + if (query_ref_boot) { if (!skip_once) ref_boot_offset += advance_older ? 1 : -1; @@ -1151,7 +1194,7 @@ static int get_boots( if (ref_boot_offset == 0) { count = 1; - query_ref_boot->id = current->id; + *query_ref_boot = current->id; break; } } else { @@ -1207,8 +1250,8 @@ static int list_boots(sd_journal *j) { static int add_boot(sd_journal *j) { char match[9+32+1] = "_BOOT_ID="; + sd_id128_t ref_boot_id; int r; - BootId ref_boot_id = {}; assert(j); @@ -1218,7 +1261,7 @@ static int add_boot(sd_journal *j) { if (arg_boot_offset == 0 && sd_id128_equal(arg_boot_id, SD_ID128_NULL)) return add_match_this_boot(j, arg_machine); - ref_boot_id.id = arg_boot_id; + ref_boot_id = arg_boot_id; r = get_boots(j, NULL, &ref_boot_id, arg_boot_offset); assert(r <= 1); if (r <= 0) { @@ -1234,7 +1277,7 @@ static int add_boot(sd_journal *j) { return r == 0 ? -ENODATA : r; } - sd_id128_to_string(ref_boot_id.id, match + 9); + sd_id128_to_string(ref_boot_id, match + 9); r = sd_journal_add_match(j, match, sizeof(match) - 1); if (r < 0) @@ -1847,7 +1890,7 @@ static int access_check(sd_journal *j) { break; default: - log_warning_errno(err, "An error was encountered while opening journal file %s, ignoring file.", path); + log_warning_errno(err, "An error was encountered while opening journal file or directory %s, ignoring file: %m", path); break; } } @@ -2103,11 +2146,61 @@ int main(int argc, char *argv[]) { if (arg_directory) r = sd_journal_open_directory(&j, arg_directory, arg_journal_type); - else if (arg_file) + else if (arg_file_stdin) { + int ifd = STDIN_FILENO; + r = sd_journal_open_files_fd(&j, &ifd, 1, 0); + } else if (arg_file) r = sd_journal_open_files(&j, (const char**) arg_file, 0); - else if (arg_machine) - r = sd_journal_open_container(&j, arg_machine, 0); - else + else if (arg_machine) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + int fd; + + if (geteuid() != 0) { + /* The file descriptor returned by OpenMachineRootDirectory() will be owned by users/groups of + * the container, thus we need root privileges to override them. */ + log_error("Using the --machine= switch requires root privileges."); + r = -EPERM; + goto finish; + } + + r = sd_bus_open_system(&bus); + if (r < 0) { + log_error_errno(r, "Failed to open system bus: %m"); + goto finish; + } + + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "OpenMachineRootDirectory", + &error, + &reply, + "s", arg_machine); + if (r < 0) { + log_error_errno(r, "Failed to open root directory: %s", bus_error_message(&error, r)); + goto finish; + } + + r = sd_bus_message_read(reply, "h", &fd); + if (r < 0) { + bus_log_parse_error(r); + goto finish; + } + + fd = fcntl(fd, F_DUPFD_CLOEXEC, 3); + if (fd < 0) { + r = log_error_errno(errno, "Failed to duplicate file descriptor: %m"); + goto finish; + } + + r = sd_journal_open_directory_fd(&j, fd, SD_JOURNAL_OS_ROOT); + if (r < 0) + safe_close(fd); + } else r = sd_journal_open(&j, !arg_merge*SD_JOURNAL_LOCAL_ONLY + arg_journal_type); if (r < 0) { log_error_errno(r, "Failed to open %s: %m", arg_directory ?: arg_file ? "files" : "journal"); @@ -2283,6 +2376,10 @@ int main(int argc, char *argv[]) { /* Opening the fd now means the first sd_journal_wait() will actually wait */ if (arg_follow) { r = sd_journal_get_fd(j); + if (r == -EMEDIUMTYPE) { + log_error_errno(r, "The --follow switch is not supported in conjunction with reading from STDIN."); + goto finish; + } if (r < 0) { log_error_errno(r, "Failed to get journal fd: %m"); goto finish; diff --git a/src/journal/journald-gperf.gperf b/src/journal/journald-gperf.gperf index c154610c54..7fecd7a964 100644 --- a/src/journal/journald-gperf.gperf +++ b/src/journal/journald-gperf.gperf @@ -19,7 +19,9 @@ Journal.Storage, config_parse_storage, 0, offsetof(Server, storage Journal.Compress, config_parse_bool, 0, offsetof(Server, compress) Journal.Seal, config_parse_bool, 0, offsetof(Server, seal) Journal.SyncIntervalSec, config_parse_sec, 0, offsetof(Server, sync_interval_usec) +# The following is a legacy name for compatibility Journal.RateLimitInterval, config_parse_sec, 0, offsetof(Server, rate_limit_interval) +Journal.RateLimitIntervalSec,config_parse_sec, 0, offsetof(Server, rate_limit_interval) Journal.RateLimitBurst, config_parse_unsigned, 0, offsetof(Server, rate_limit_burst) Journal.SystemMaxUse, config_parse_iec_uint64, 0, offsetof(Server, system_metrics.max_use) Journal.SystemMaxFileSize, config_parse_iec_uint64, 0, offsetof(Server, system_metrics.max_size) diff --git a/src/journal/journald-native.c b/src/journal/journald-native.c index 3d8f05996b..a445291a5e 100644 --- a/src/journal/journald-native.c +++ b/src/journal/journald-native.c @@ -206,7 +206,7 @@ void server_process_native_message( allow_object_pid(ucred)) { char buf[DECIMAL_STR_MAX(pid_t)]; memcpy(buf, p + strlen("OBJECT_PID="), l - strlen("OBJECT_PID=")); - char_array_0(buf); + buf[l-strlen("OBJECT_PID=")] = '\0'; /* ignore error */ parse_pid(buf, &object_pid); diff --git a/src/journal/journald-server.c b/src/journal/journald-server.c index 8089bb5883..e14d0ad980 100644 --- a/src/journal/journald-server.c +++ b/src/journal/journald-server.c @@ -253,7 +253,7 @@ static int open_journal( if (reliably) r = journal_file_open_reliably(fname, flags, 0640, s->compress, seal, metrics, s->mmap, s->deferred_closes, NULL, &f); else - r = journal_file_open(fname, flags, 0640, s->compress, seal, metrics, s->mmap, s->deferred_closes, NULL, &f); + r = journal_file_open(-1, fname, flags, 0640, s->compress, seal, metrics, s->mmap, s->deferred_closes, NULL, &f); if (r < 0) return r; diff --git a/src/journal/journald.conf b/src/journal/journald.conf index 7beb96c671..2541b949be 100644 --- a/src/journal/journald.conf +++ b/src/journal/journald.conf @@ -17,7 +17,7 @@ #Seal=yes #SplitMode=uid #SyncIntervalSec=5m -#RateLimitInterval=30s +#RateLimitIntervalSec=30s #RateLimitBurst=1000 #SystemMaxUse= #SystemKeepFree= diff --git a/src/journal/sd-journal.c b/src/journal/sd-journal.c index 3c21d4129e..27c1dd346f 100644 --- a/src/journal/sd-journal.c +++ b/src/journal/sd-journal.c @@ -1233,14 +1233,37 @@ static bool file_type_wanted(int flags, const char *filename) { return false; } -static int add_any_file(sd_journal *j, const char *path) { +static bool path_has_prefix(sd_journal *j, const char *path, const char *prefix) { + assert(j); + assert(path); + assert(prefix); + + if (j->toplevel_fd >= 0) + return false; + + return path_startswith(path, prefix); +} + +static const char *skip_slash(const char *p) { + + if (!p) + return NULL; + + while (*p == '/') + p++; + + return p; +} + +static int add_any_file(sd_journal *j, int fd, const char *path) { JournalFile *f = NULL; + bool close_fd = false; int r, k; assert(j); - assert(path); + assert(fd >= 0 || path); - if (ordered_hashmap_get(j->files, path)) + if (path && ordered_hashmap_get(j->files, path)) return 0; if (ordered_hashmap_size(j->files) >= JOURNAL_FILES_MAX) { @@ -1249,8 +1272,24 @@ static int add_any_file(sd_journal *j, const char *path) { goto fail; } - r = journal_file_open(path, O_RDONLY, 0, false, false, NULL, j->mmap, NULL, NULL, &f); + if (fd < 0 && j->toplevel_fd >= 0) { + + /* If there's a top-level fd defined, open the file relative to this now. (Make the path relative, + * explicitly, since otherwise openat() ignores the first argument.) */ + + fd = openat(j->toplevel_fd, skip_slash(path), O_RDONLY|O_CLOEXEC); + if (fd < 0) { + r = log_debug_errno(errno, "Failed to open journal file %s: %m", path); + goto fail; + } + + close_fd = true; + } + + r = journal_file_open(fd, path, O_RDONLY, 0, false, false, NULL, j->mmap, NULL, NULL, &f); if (r < 0) { + if (close_fd) + safe_close(fd); log_debug_errno(r, "Failed to open journal file %s: %m", path); goto fail; } @@ -1259,10 +1298,16 @@ static int add_any_file(sd_journal *j, const char *path) { r = ordered_hashmap_put(j->files, f->path, f); if (r < 0) { + f->close_fd = close_fd; (void) journal_file_close(f); goto fail; } + if (!j->has_runtime_files && path_has_prefix(j, f->path, "/run")) + j->has_runtime_files = true; + else if (!j->has_persistent_files && path_has_prefix(j, f->path, "/var")) + j->has_persistent_files = true; + log_debug("File %s added.", f->path); check_network(j, f->fd); @@ -1286,18 +1331,14 @@ static int add_file(sd_journal *j, const char *prefix, const char *filename) { assert(prefix); assert(filename); - if (j->no_new_files || - !file_type_wanted(j->flags, filename)) + if (j->no_new_files) + return 0; + + if (!file_type_wanted(j->flags, filename)) return 0; path = strjoina(prefix, "/", filename); - - if (!j->has_runtime_files && path_startswith(path, "/run/log/journal")) - j->has_runtime_files = true; - else if (!j->has_persistent_files && path_startswith(path, "/var/log/journal")) - j->has_persistent_files = true; - - return add_any_file(j, path); + return add_any_file(j, -1, path); } static void remove_file(sd_journal *j, const char *prefix, const char *filename) { @@ -1373,21 +1414,33 @@ static int add_directory(sd_journal *j, const char *prefix, const char *dirname) assert(j); assert(prefix); - assert(dirname); - - log_debug("Considering %s/%s.", prefix, dirname); - if ((j->flags & SD_JOURNAL_LOCAL_ONLY) && - !(dirname_is_machine_id(dirname) > 0 || path_startswith(prefix, "/run"))) - return 0; + /* Adds a journal file directory to watch. If the directory is already tracked this updates the inotify watch + * and reenumerates directory contents */ - path = strjoin(prefix, "/", dirname, NULL); + if (dirname) + path = strjoin(prefix, "/", dirname, NULL); + else + path = strdup(prefix); if (!path) { r = -ENOMEM; goto fail; } - d = opendir(path); + log_debug("Considering directory %s.", path); + + /* We consider everything local that is in a directory for the local machine ID, or that is stored in /run */ + if ((j->flags & SD_JOURNAL_LOCAL_ONLY) && + !((dirname && dirname_is_machine_id(dirname) > 0) || path_has_prefix(j, path, "/run"))) + return 0; + + + if (j->toplevel_fd < 0) + d = opendir(path); + else + /* Open the specified directory relative to the the toplevel fd. Enforce that the path specified is + * relative, by dropping the initial slash */ + d = xopendirat(j->toplevel_fd, skip_slash(path), 0); if (!d) { r = log_debug_errno(errno, "Failed to open directory %s: %m", path); goto fail; @@ -1419,17 +1472,18 @@ static int add_directory(sd_journal *j, const char *prefix, const char *dirname) return 0; if (m->wd <= 0 && j->inotify_fd >= 0) { + /* Watch this directory, if it not being watched yet. */ - m->wd = inotify_add_watch(j->inotify_fd, m->path, - IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB|IN_DELETE| - IN_DELETE_SELF|IN_MOVE_SELF|IN_UNMOUNT|IN_MOVED_FROM| - IN_ONLYDIR); + m->wd = inotify_add_watch_fd(j->inotify_fd, dirfd(d), + IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB|IN_DELETE| + IN_DELETE_SELF|IN_MOVE_SELF|IN_UNMOUNT|IN_MOVED_FROM| + IN_ONLYDIR); if (m->wd > 0 && hashmap_put(j->directories_by_wd, INT_TO_PTR(m->wd), m) < 0) inotify_rm_watch(j->inotify_fd, m->wd); } - FOREACH_DIRENT_ALL(de, d, return log_debug_errno(errno, "Failed to read directory %s: %m", m->path)) { + FOREACH_DIRENT_ALL(de, d, r = log_debug_errno(errno, "Failed to read directory %s: %m", m->path); goto fail) { if (dirent_is_file_with_suffix(de, ".journal") || dirent_is_file_with_suffix(de, ".journal~")) @@ -1441,7 +1495,7 @@ static int add_directory(sd_journal *j, const char *prefix, const char *dirname) return 0; fail: - k = journal_put_error(j, r, path ?: dirname); + k = journal_put_error(j, r, path ?: prefix); if (k < 0) return k; @@ -1449,28 +1503,62 @@ fail: } static int add_root_directory(sd_journal *j, const char *p, bool missing_ok) { + _cleanup_closedir_ DIR *d = NULL; struct dirent *de; Directory *m; int r, k; assert(j); - assert(p); - if ((j->flags & SD_JOURNAL_RUNTIME_ONLY) && - !path_startswith(p, "/run")) - return -EINVAL; + /* Adds a root directory to our set of directories to use. If the root directory is already in the set, we + * update the inotify logic, and renumerate the directory entries. This call may hence be called to initially + * populate the set, as well as to update it later. */ - if (j->prefix) - p = strjoina(j->prefix, p); + if (p) { + /* If there's a path specified, use it. */ - d = opendir(p); - if (!d) { - if (errno == ENOENT && missing_ok) - return 0; + if ((j->flags & SD_JOURNAL_RUNTIME_ONLY) && + !path_has_prefix(j, p, "/run")) + return -EINVAL; - r = log_debug_errno(errno, "Failed to open root directory %s: %m", p); - goto fail; + if (j->prefix) + p = strjoina(j->prefix, p); + + if (j->toplevel_fd < 0) + d = opendir(p); + else + d = xopendirat(j->toplevel_fd, skip_slash(p), 0); + + if (!d) { + if (errno == ENOENT && missing_ok) + return 0; + + r = log_debug_errno(errno, "Failed to open root directory %s: %m", p); + goto fail; + } + } else { + int dfd; + + /* If there's no path specified, then we use the top-level fd itself. We duplicate the fd here, since + * opendir() will take possession of the fd, and close it, which we don't want. */ + + p = "."; /* store this as "." in the directories hashmap */ + + dfd = fcntl(j->toplevel_fd, F_DUPFD_CLOEXEC, 3); + if (dfd < 0) { + r = -errno; + goto fail; + } + + d = fdopendir(dfd); + if (!d) { + r = -errno; + safe_close(dfd); + goto fail; + } + + rewinddir(d); } m = hashmap_get(j->directories_by_path, p); @@ -1482,6 +1570,7 @@ static int add_root_directory(sd_journal *j, const char *p, bool missing_ok) { } m->is_root = true; + m->path = strdup(p); if (!m->path) { free(m); @@ -1505,7 +1594,7 @@ static int add_root_directory(sd_journal *j, const char *p, bool missing_ok) { if (m->wd <= 0 && j->inotify_fd >= 0) { - m->wd = inotify_add_watch(j->inotify_fd, m->path, + m->wd = inotify_add_watch_fd(j->inotify_fd, dirfd(d), IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB|IN_DELETE| IN_ONLYDIR); @@ -1516,7 +1605,7 @@ static int add_root_directory(sd_journal *j, const char *p, bool missing_ok) { if (j->no_new_files) return 0; - FOREACH_DIRENT_ALL(de, d, return log_debug_errno(errno, "Failed to read directory %s: %m", m->path)) { + FOREACH_DIRENT_ALL(de, d, r = log_debug_errno(errno, "Failed to read directory %s: %m", m->path); goto fail) { sd_id128_t id; if (dirent_is_file_with_suffix(de, ".journal") || @@ -1585,8 +1674,7 @@ static int add_current_paths(sd_journal *j) { assert(j); assert(j->no_new_files); - /* Simply adds all directories for files we have open as - * "root" directories. We don't expect errors here, so we + /* Simply adds all directories for files we have open as directories. We don't expect errors here, so we * treat them as fatal. */ ORDERED_HASHMAP_FOREACH(f, j->files, i) { @@ -1597,7 +1685,7 @@ static int add_current_paths(sd_journal *j) { if (!dir) return -ENOMEM; - r = add_root_directory(j, dir, true); + r = add_directory(j, dir, NULL); if (r < 0) return r; } @@ -1614,13 +1702,7 @@ static int allocate_inotify(sd_journal *j) { return -errno; } - if (!j->directories_by_wd) { - j->directories_by_wd = hashmap_new(NULL); - if (!j->directories_by_wd) - return -ENOMEM; - } - - return 0; + return hashmap_ensure_allocated(&j->directories_by_wd, NULL); } static sd_journal *journal_new(int flags, const char *path) { @@ -1631,6 +1713,7 @@ static sd_journal *journal_new(int flags, const char *path) { return NULL; j->original_pid = getpid(); + j->toplevel_fd = -1; j->inotify_fd = -1; j->flags = flags; j->data_threshold = DEFAULT_DATA_THRESHOLD; @@ -1684,6 +1767,9 @@ _public_ int sd_journal_open_container(sd_journal **ret, const char *machine, in char *p; int r; + /* This is pretty much deprecated, people should use machined's OpenMachineRootDirectory() call instead in + * combination with sd_journal_open_directory_fd(). */ + assert_return(machine, -EINVAL); assert_return(ret, -EINVAL); assert_return((flags & ~(SD_JOURNAL_LOCAL_ONLY|SD_JOURNAL_SYSTEM)) == 0, -EINVAL); @@ -1726,13 +1812,16 @@ _public_ int sd_journal_open_directory(sd_journal **ret, const char *path, int f assert_return(ret, -EINVAL); assert_return(path, -EINVAL); - assert_return(flags == 0, -EINVAL); + assert_return((flags & ~SD_JOURNAL_OS_ROOT) == 0, -EINVAL); j = journal_new(flags, path); if (!j) return -ENOMEM; - r = add_root_directory(j, path, false); + if (flags & SD_JOURNAL_OS_ROOT) + r = add_search_paths(j); + else + r = add_root_directory(j, path, false); if (r < 0) goto fail; @@ -1741,7 +1830,6 @@ _public_ int sd_journal_open_directory(sd_journal **ret, const char *path, int f fail: sd_journal_close(j); - return r; } @@ -1758,7 +1846,7 @@ _public_ int sd_journal_open_files(sd_journal **ret, const char **paths, int fla return -ENOMEM; STRV_FOREACH(path, paths) { - r = add_any_file(j, *path); + r = add_any_file(j, -1, *path); if (r < 0) goto fail; } @@ -1770,7 +1858,96 @@ _public_ int sd_journal_open_files(sd_journal **ret, const char **paths, int fla fail: sd_journal_close(j); + return r; +} + +_public_ int sd_journal_open_directory_fd(sd_journal **ret, int fd, int flags) { + sd_journal *j; + struct stat st; + int r; + + assert_return(ret, -EINVAL); + assert_return(fd >= 0, -EBADF); + assert_return((flags & ~SD_JOURNAL_OS_ROOT) == 0, -EINVAL); + + if (fstat(fd, &st) < 0) + return -errno; + + if (!S_ISDIR(st.st_mode)) + return -EBADFD; + + j = journal_new(flags, NULL); + if (!j) + return -ENOMEM; + + j->toplevel_fd = fd; + + if (flags & SD_JOURNAL_OS_ROOT) + r = add_search_paths(j); + else + r = add_root_directory(j, NULL, false); + if (r < 0) + goto fail; + *ret = j; + return 0; + +fail: + sd_journal_close(j); + return r; +} + +_public_ int sd_journal_open_files_fd(sd_journal **ret, int fds[], unsigned n_fds, int flags) { + Iterator iterator; + JournalFile *f; + sd_journal *j; + unsigned i; + int r; + + assert_return(ret, -EINVAL); + assert_return(n_fds > 0, -EBADF); + assert_return(flags == 0, -EINVAL); + + j = journal_new(flags, NULL); + if (!j) + return -ENOMEM; + + for (i = 0; i < n_fds; i++) { + struct stat st; + + if (fds[i] < 0) { + r = -EBADF; + goto fail; + } + + if (fstat(fds[i], &st) < 0) { + r = -errno; + goto fail; + } + + if (!S_ISREG(st.st_mode)) { + r = -EBADFD; + goto fail; + } + + r = add_any_file(j, fds[i], NULL); + if (r < 0) + goto fail; + } + + j->no_new_files = true; + j->no_inotify = true; + + *ret = j; + return 0; + +fail: + /* If we fail, make sure we don't take possession of the files we managed to make use of successfuly, and they + * remain open */ + ORDERED_HASHMAP_FOREACH(f, j->files, iterator) + f->close_fd = false; + + sd_journal_close(j); return r; } @@ -2097,6 +2274,9 @@ _public_ int sd_journal_get_fd(sd_journal *j) { assert_return(j, -EINVAL); assert_return(!journal_pid_changed(j), -ECHILD); + if (j->no_inotify) + return -EMEDIUMTYPE; + if (j->inotify_fd >= 0) return j->inotify_fd; @@ -2104,10 +2284,14 @@ _public_ int sd_journal_get_fd(sd_journal *j) { if (r < 0) return r; + log_debug("Reiterating files to get inotify watches established"); + /* Iterate through all dirs again, to add them to the * inotify */ if (j->no_new_files) r = add_current_paths(j); + else if (j->toplevel_fd >= 0) + r = add_root_directory(j, NULL, false); else if (j->path) r = add_root_directory(j, j->path, true); else diff --git a/src/journal/test-journal-flush.c b/src/journal/test-journal-flush.c index 93dc0e0d81..ba8b20b228 100644 --- a/src/journal/test-journal-flush.c +++ b/src/journal/test-journal-flush.c @@ -38,7 +38,7 @@ int main(int argc, char *argv[]) { assert_se(mkdtemp(dn)); fn = strappend(dn, "/test.journal"); - r = journal_file_open(fn, O_CREAT|O_RDWR, 0644, false, false, NULL, NULL, NULL, NULL, &new_journal); + r = journal_file_open(-1, fn, O_CREAT|O_RDWR, 0644, false, false, NULL, NULL, NULL, NULL, &new_journal); assert_se(r >= 0); r = sd_journal_open(&j, 0); diff --git a/src/journal/test-journal-interleaving.c b/src/journal/test-journal-interleaving.c index f887f43f0d..5e063f4d04 100644 --- a/src/journal/test-journal-interleaving.c +++ b/src/journal/test-journal-interleaving.c @@ -52,7 +52,7 @@ noreturn static void log_assert_errno(const char *text, int eno, const char *fil static JournalFile *test_open(const char *name) { JournalFile *f; - assert_ret(journal_file_open(name, O_RDWR|O_CREAT, 0644, true, false, NULL, NULL, NULL, NULL, &f)); + assert_ret(journal_file_open(-1, name, O_RDWR|O_CREAT, 0644, true, false, NULL, NULL, NULL, NULL, &f)); return f; } @@ -216,7 +216,7 @@ static void test_sequence_numbers(void) { assert_se(mkdtemp(t)); assert_se(chdir(t) >= 0); - assert_se(journal_file_open("one.journal", O_RDWR|O_CREAT, 0644, + assert_se(journal_file_open(-1, "one.journal", O_RDWR|O_CREAT, 0644, true, false, NULL, NULL, NULL, NULL, &one) == 0); append_number(one, 1, &seqnum); @@ -233,7 +233,7 @@ static void test_sequence_numbers(void) { memcpy(&seqnum_id, &one->header->seqnum_id, sizeof(sd_id128_t)); - assert_se(journal_file_open("two.journal", O_RDWR|O_CREAT, 0644, + assert_se(journal_file_open(-1, "two.journal", O_RDWR|O_CREAT, 0644, true, false, NULL, NULL, NULL, one, &two) == 0); assert_se(two->header->state == STATE_ONLINE); @@ -264,7 +264,7 @@ static void test_sequence_numbers(void) { /* restart server */ seqnum = 0; - assert_se(journal_file_open("two.journal", O_RDWR, 0, + assert_se(journal_file_open(-1, "two.journal", O_RDWR, 0, true, false, NULL, NULL, NULL, NULL, &two) == 0); assert_se(sd_id128_equal(two->header->seqnum_id, seqnum_id)); diff --git a/src/journal/test-journal-stream.c b/src/journal/test-journal-stream.c index 839ea5a9a5..7e5a980719 100644 --- a/src/journal/test-journal-stream.c +++ b/src/journal/test-journal-stream.c @@ -92,9 +92,9 @@ int main(int argc, char *argv[]) { assert_se(mkdtemp(t)); assert_se(chdir(t) >= 0); - assert_se(journal_file_open("one.journal", O_RDWR|O_CREAT, 0666, true, false, NULL, NULL, NULL, NULL, &one) == 0); - assert_se(journal_file_open("two.journal", O_RDWR|O_CREAT, 0666, true, false, NULL, NULL, NULL, NULL, &two) == 0); - assert_se(journal_file_open("three.journal", O_RDWR|O_CREAT, 0666, true, false, NULL, NULL, NULL, NULL, &three) == 0); + assert_se(journal_file_open(-1, "one.journal", O_RDWR|O_CREAT, 0666, true, false, NULL, NULL, NULL, NULL, &one) == 0); + assert_se(journal_file_open(-1, "two.journal", O_RDWR|O_CREAT, 0666, true, false, NULL, NULL, NULL, NULL, &two) == 0); + assert_se(journal_file_open(-1, "three.journal", O_RDWR|O_CREAT, 0666, true, false, NULL, NULL, NULL, NULL, &three) == 0); for (i = 0; i < N_ENTRIES; i++) { char *p, *q; diff --git a/src/journal/test-journal-verify.c b/src/journal/test-journal-verify.c index 6b4643cd25..3d2312fc55 100644 --- a/src/journal/test-journal-verify.c +++ b/src/journal/test-journal-verify.c @@ -55,7 +55,7 @@ static int raw_verify(const char *fn, const char *verification_key) { JournalFile *f; int r; - r = journal_file_open(fn, O_RDONLY, 0666, true, !!verification_key, NULL, NULL, NULL, NULL, &f); + r = journal_file_open(-1, fn, O_RDONLY, 0666, true, !!verification_key, NULL, NULL, NULL, NULL, &f); if (r < 0) return r; @@ -88,7 +88,7 @@ int main(int argc, char *argv[]) { log_info("Generating..."); - assert_se(journal_file_open("test.journal", O_RDWR|O_CREAT, 0666, true, !!verification_key, NULL, NULL, NULL, NULL, &f) == 0); + assert_se(journal_file_open(-1, "test.journal", O_RDWR|O_CREAT, 0666, true, !!verification_key, NULL, NULL, NULL, NULL, &f) == 0); for (n = 0; n < N_ENTRIES; n++) { struct iovec iovec; @@ -111,7 +111,7 @@ int main(int argc, char *argv[]) { log_info("Verifying..."); - assert_se(journal_file_open("test.journal", O_RDONLY, 0666, true, !!verification_key, NULL, NULL, NULL, NULL, &f) == 0); + assert_se(journal_file_open(-1, "test.journal", O_RDONLY, 0666, true, !!verification_key, NULL, NULL, NULL, NULL, &f) == 0); /* journal_file_print_header(f); */ journal_file_dump(f); diff --git a/src/journal/test-journal.c b/src/journal/test-journal.c index ea685af782..2543d64b5b 100644 --- a/src/journal/test-journal.c +++ b/src/journal/test-journal.c @@ -42,7 +42,7 @@ static void test_non_empty(void) { assert_se(mkdtemp(t)); assert_se(chdir(t) >= 0); - assert_se(journal_file_open("test.journal", O_RDWR|O_CREAT, 0666, true, true, NULL, NULL, NULL, NULL, &f) == 0); + assert_se(journal_file_open(-1, "test.journal", O_RDWR|O_CREAT, 0666, true, true, NULL, NULL, NULL, NULL, &f) == 0); dual_timestamp_get(&ts); @@ -131,13 +131,13 @@ static void test_empty(void) { assert_se(mkdtemp(t)); assert_se(chdir(t) >= 0); - assert_se(journal_file_open("test.journal", O_RDWR|O_CREAT, 0666, false, false, NULL, NULL, NULL, NULL, &f1) == 0); + assert_se(journal_file_open(-1, "test.journal", O_RDWR|O_CREAT, 0666, false, false, NULL, NULL, NULL, NULL, &f1) == 0); - assert_se(journal_file_open("test-compress.journal", O_RDWR|O_CREAT, 0666, true, false, NULL, NULL, NULL, NULL, &f2) == 0); + assert_se(journal_file_open(-1, "test-compress.journal", O_RDWR|O_CREAT, 0666, true, false, NULL, NULL, NULL, NULL, &f2) == 0); - assert_se(journal_file_open("test-seal.journal", O_RDWR|O_CREAT, 0666, false, true, NULL, NULL, NULL, NULL, &f3) == 0); + assert_se(journal_file_open(-1, "test-seal.journal", O_RDWR|O_CREAT, 0666, false, true, NULL, NULL, NULL, NULL, &f3) == 0); - assert_se(journal_file_open("test-seal-compress.journal", O_RDWR|O_CREAT, 0666, true, true, NULL, NULL, NULL, NULL, &f4) == 0); + assert_se(journal_file_open(-1, "test-seal-compress.journal", O_RDWR|O_CREAT, 0666, true, true, NULL, NULL, NULL, NULL, &f4) == 0); journal_file_print_header(f1); puts(""); diff --git a/src/libsystemd/libsystemd.sym b/src/libsystemd/libsystemd.sym index 4ab637b686..0b3a1708dc 100644 --- a/src/libsystemd/libsystemd.sym +++ b/src/libsystemd/libsystemd.sym @@ -489,3 +489,9 @@ global: sd_journal_enumerate_fields; sd_journal_restart_fields; } LIBSYSTEMD_227; + +LIBSYSTEMD_230 { +global: + sd_journal_open_directory_fd; + sd_journal_open_files_fd; +} LIBSYSTEMD_229; diff --git a/src/locale/language-fallback-map b/src/locale/language-fallback-map index 6aadda091a..d0b02a6b98 100644 --- a/src/locale/language-fallback-map +++ b/src/locale/language-fallback-map @@ -3,6 +3,10 @@ en_AU en_AU:en_GB en_IE en_IE:en_GB en_NZ en_NZ:en_GB en_ZA en_ZA:en_GB +fr_BE fr_BE:fr_FR +fr_CA fr_CA:fr_FR +fr_CH fr_CH:fr_FR +fr_LU fr_LU:fr_FR it_CH it_CH:it_IT mai_IN mai:hi nds_DE nds:de diff --git a/src/login/systemd-user.m4 b/src/login/systemd-user.m4 index 7933508f2b..f188a8e548 100644 --- a/src/login/systemd-user.m4 +++ b/src/login/systemd-user.m4 @@ -8,4 +8,5 @@ m4_ifdef(`HAVE_SELINUX', session required pam_selinux.so close session required pam_selinux.so nottys open )m4_dnl +session required pam_loginuid.so session include system-auth diff --git a/src/machine/image-dbus.c b/src/machine/image-dbus.c index 73f5112c4d..0eed9b81bb 100644 --- a/src/machine/image-dbus.c +++ b/src/machine/image-dbus.c @@ -20,9 +20,11 @@ #include "alloc-util.h" #include "bus-label.h" #include "bus-util.h" +#include "fd-util.h" #include "image-dbus.h" #include "io-util.h" #include "machine-image.h" +#include "process-util.h" #include "strv.h" #include "user-util.h" @@ -33,13 +35,18 @@ int bus_image_method_remove( void *userdata, sd_bus_error *error) { + _cleanup_close_pair_ int errno_pipe_fd[2] = { -1, -1 }; Image *image = userdata; Manager *m = image->userdata; + pid_t child; int r; assert(message); assert(image); + if (m->n_operations >= OPERATIONS_MAX) + return sd_bus_error_setf(error, SD_BUS_ERROR_LIMITS_EXCEEDED, "Too many ongoing operations."); + r = bus_verify_polkit_async( message, CAP_SYS_ADMIN, @@ -54,11 +61,35 @@ int bus_image_method_remove( if (r == 0) return 1; /* Will call us back */ - r = image_remove(image); - if (r < 0) + if (pipe2(errno_pipe_fd, O_CLOEXEC|O_NONBLOCK) < 0) + return sd_bus_error_set_errnof(error, errno, "Failed to create pipe: %m"); + + child = fork(); + if (child < 0) + return sd_bus_error_set_errnof(error, errno, "Failed to fork(): %m"); + if (child == 0) { + errno_pipe_fd[0] = safe_close(errno_pipe_fd[0]); + + r = image_remove(image); + if (r < 0) { + (void) write(errno_pipe_fd[1], &r, sizeof(r)); + _exit(EXIT_FAILURE); + } + + _exit(EXIT_SUCCESS); + } + + errno_pipe_fd[1] = safe_close(errno_pipe_fd[1]); + + r = operation_new(m, NULL, child, message, errno_pipe_fd[0]); + if (r < 0) { + (void) sigkill_wait(child); return r; + } - return sd_bus_reply_method_return(message, NULL); + errno_pipe_fd[0] = -1; + + return 1; } int bus_image_method_rename( @@ -107,13 +138,19 @@ int bus_image_method_clone( void *userdata, sd_bus_error *error) { + _cleanup_close_pair_ int errno_pipe_fd[2] = { -1, -1 }; Image *image = userdata; Manager *m = image->userdata; const char *new_name; int r, read_only; + pid_t child; assert(message); assert(image); + assert(m); + + if (m->n_operations >= OPERATIONS_MAX) + return sd_bus_error_setf(error, SD_BUS_ERROR_LIMITS_EXCEEDED, "Too many ongoing operations."); r = sd_bus_message_read(message, "sb", &new_name, &read_only); if (r < 0) @@ -136,11 +173,35 @@ int bus_image_method_clone( if (r == 0) return 1; /* Will call us back */ - r = image_clone(image, new_name, read_only); - if (r < 0) + if (pipe2(errno_pipe_fd, O_CLOEXEC|O_NONBLOCK) < 0) + return sd_bus_error_set_errnof(error, errno, "Failed to create pipe: %m"); + + child = fork(); + if (child < 0) + return sd_bus_error_set_errnof(error, errno, "Failed to fork(): %m"); + if (child == 0) { + errno_pipe_fd[0] = safe_close(errno_pipe_fd[0]); + + r = image_clone(image, new_name, read_only); + if (r < 0) { + (void) write(errno_pipe_fd[1], &r, sizeof(r)); + _exit(EXIT_FAILURE); + } + + _exit(EXIT_SUCCESS); + } + + errno_pipe_fd[1] = safe_close(errno_pipe_fd[1]); + + r = operation_new(m, NULL, child, message, errno_pipe_fd[0]); + if (r < 0) { + (void) sigkill_wait(child); return r; + } - return sd_bus_reply_method_return(message, NULL); + errno_pipe_fd[0] = -1; + + return 1; } int bus_image_method_mark_read_only( diff --git a/src/machine/machine-dbus.c b/src/machine/machine-dbus.c index ab54d9e934..7b9aa66d63 100644 --- a/src/machine/machine-dbus.c +++ b/src/machine/machine-dbus.c @@ -1085,52 +1085,11 @@ finish: return r; } -static int machine_operation_done(sd_event_source *s, const siginfo_t *si, void *userdata) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - MachineOperation *o = userdata; - int r; - - assert(o); - assert(si); - - o->pid = 0; - - if (si->si_code != CLD_EXITED) { - r = sd_bus_error_setf(&error, SD_BUS_ERROR_FAILED, "Child died abnormally."); - goto fail; - } - - if (si->si_status != EXIT_SUCCESS) { - if (read(o->errno_fd, &r, sizeof(r)) == sizeof(r)) - r = sd_bus_error_set_errnof(&error, r, "%m"); - else - r = sd_bus_error_setf(&error, SD_BUS_ERROR_FAILED, "Child failed."); - - goto fail; - } - - r = sd_bus_reply_method_return(o->message, NULL); - if (r < 0) - log_error_errno(r, "Failed to reply to message: %m"); - - machine_operation_unref(o); - return 0; - -fail: - r = sd_bus_reply_method_error(o->message, &error); - if (r < 0) - log_error_errno(r, "Failed to reply to message: %m"); - - machine_operation_unref(o); - return 0; -} - int bus_machine_method_copy(sd_bus_message *message, void *userdata, sd_bus_error *error) { const char *src, *dest, *host_path, *container_path, *host_basename, *host_dirname, *container_basename, *container_dirname; _cleanup_close_pair_ int errno_pipe_fd[2] = { -1, -1 }; _cleanup_close_ int hostfd = -1; Machine *m = userdata; - MachineOperation *o; bool copy_from; pid_t child; char *t; @@ -1139,7 +1098,7 @@ int bus_machine_method_copy(sd_bus_message *message, void *userdata, sd_bus_erro assert(message); assert(m); - if (m->n_operations >= MACHINE_OPERATIONS_MAX) + if (m->manager->n_operations >= OPERATIONS_MAX) return sd_bus_error_setf(error, SD_BUS_ERROR_LIMITS_EXCEEDED, "Too many ongoing copies."); if (m->class != MACHINE_CONTAINER) @@ -1249,29 +1208,107 @@ int bus_machine_method_copy(sd_bus_message *message, void *userdata, sd_bus_erro errno_pipe_fd[1] = safe_close(errno_pipe_fd[1]); - /* Copying might take a while, hence install a watch the - * child, and return */ + /* Copying might take a while, hence install a watch on the child, and return */ - o = new0(MachineOperation, 1); - if (!o) - return log_oom(); - - o->pid = child; - o->message = sd_bus_message_ref(message); - o->errno_fd = errno_pipe_fd[0]; + r = operation_new(m->manager, m, child, message, errno_pipe_fd[0]); + if (r < 0) { + (void) sigkill_wait(child); + return r; + } errno_pipe_fd[0] = -1; - r = sd_event_add_child(m->manager->event, &o->event_source, child, WEXITED, machine_operation_done, o); - if (r < 0) { - machine_operation_unref(o); - return log_oom(); + return 1; +} + +int bus_machine_method_open_root_directory(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_close_ int fd = -1; + Machine *m = userdata; + int r; + + assert(message); + assert(m); + + r = bus_verify_polkit_async( + message, + CAP_SYS_ADMIN, + "org.freedesktop.machine1.manage-machines", + NULL, + false, + UID_INVALID, + &m->manager->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Will call us back */ + + switch (m->class) { + + case MACHINE_HOST: + fd = open("/", O_RDONLY|O_CLOEXEC|O_DIRECTORY); + if (fd < 0) + return -errno; + + break; + + case MACHINE_CONTAINER: { + _cleanup_close_ int mntns_fd = -1, root_fd = -1; + _cleanup_close_pair_ int pair[2] = { -1, -1 }; + siginfo_t si; + pid_t child; + + r = namespace_open(m->leader, NULL, &mntns_fd, NULL, NULL, &root_fd); + if (r < 0) + return r; + + if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) < 0) + return -errno; + + child = fork(); + if (child < 0) + return sd_bus_error_set_errnof(error, errno, "Failed to fork(): %m"); + + if (child == 0) { + _cleanup_close_ int dfd = -1; + + pair[0] = safe_close(pair[0]); + + r = namespace_enter(-1, mntns_fd, -1, -1, root_fd); + if (r < 0) + _exit(EXIT_FAILURE); + + dfd = open("/", O_RDONLY|O_CLOEXEC|O_DIRECTORY); + if (dfd < 0) + _exit(EXIT_FAILURE); + + r = send_one_fd(pair[1], dfd, 0); + dfd = safe_close(dfd); + if (r < 0) + _exit(EXIT_FAILURE); + + _exit(EXIT_SUCCESS); + } + + pair[1] = safe_close(pair[1]); + + r = wait_for_terminate(child, &si); + if (r < 0) + return sd_bus_error_set_errnof(error, r, "Failed to wait for child: %m"); + if (si.si_code != CLD_EXITED || si.si_status != EXIT_SUCCESS) + return sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Child died abnormally."); + + fd = receive_one_fd(pair[0], MSG_DONTWAIT); + if (fd < 0) + return fd; + + break; } - LIST_PREPEND(operations, m->operations, o); - m->n_operations++; - o->machine = m; + default: + return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Opening the root directory is only supported on container machines."); + } - return 1; + return sd_bus_reply_method_return(message, "h", fd); } const sd_bus_vtable machine_vtable[] = { @@ -1297,6 +1334,7 @@ const sd_bus_vtable machine_vtable[] = { SD_BUS_METHOD("BindMount", "ssbb", NULL, bus_machine_method_bind_mount, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("CopyFrom", "ss", NULL, bus_machine_method_copy, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("CopyTo", "ss", NULL, bus_machine_method_copy, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("OpenRootDirectory", NULL, "h", bus_machine_method_open_root_directory, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_VTABLE_END }; diff --git a/src/machine/machine-dbus.h b/src/machine/machine-dbus.h index 3a8162b171..241b23c7ec 100644 --- a/src/machine/machine-dbus.h +++ b/src/machine/machine-dbus.h @@ -38,6 +38,7 @@ int bus_machine_method_open_login(sd_bus_message *message, void *userdata, sd_bu int bus_machine_method_open_shell(sd_bus_message *message, void *userdata, sd_bus_error *error); int bus_machine_method_bind_mount(sd_bus_message *message, void *userdata, sd_bus_error *error); int bus_machine_method_copy(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_machine_method_open_root_directory(sd_bus_message *message, void *userdata, sd_bus_error *error); int machine_send_signal(Machine *m, bool new_machine); int machine_send_create_reply(Machine *m, sd_bus_error *error); diff --git a/src/machine/machine.c b/src/machine/machine.c index 7d4270a8ff..c1fae57084 100644 --- a/src/machine/machine.c +++ b/src/machine/machine.c @@ -89,7 +89,7 @@ void machine_free(Machine *m) { assert(m); while (m->operations) - machine_operation_unref(m->operations); + operation_free(m->operations); if (m->in_gc_queue) LIST_REMOVE(gc_queue, m->manager->machine_gc_queue, m); @@ -596,28 +596,6 @@ int machine_open_terminal(Machine *m, const char *path, int mode) { } } -MachineOperation *machine_operation_unref(MachineOperation *o) { - if (!o) - return NULL; - - sd_event_source_unref(o->event_source); - - safe_close(o->errno_fd); - - if (o->pid > 1) - (void) kill(o->pid, SIGKILL); - - sd_bus_message_unref(o->message); - - if (o->machine) { - LIST_REMOVE(operations, o->machine->operations, o); - o->machine->n_operations--; - } - - free(o); - return NULL; -} - void machine_release_unit(Machine *m) { assert(m); diff --git a/src/machine/machine.h b/src/machine/machine.h index 1d8cc5911a..e5d75361a9 100644 --- a/src/machine/machine.h +++ b/src/machine/machine.h @@ -20,11 +20,11 @@ ***/ typedef struct Machine Machine; -typedef struct MachineOperation MachineOperation; typedef enum KillWho KillWho; #include "list.h" #include "machined.h" +#include "operation.h" typedef enum MachineState { MACHINE_OPENING, /* Machine is being registered */ @@ -49,17 +49,6 @@ enum KillWho { _KILL_WHO_INVALID = -1 }; -#define MACHINE_OPERATIONS_MAX 64 - -struct MachineOperation { - Machine *machine; - pid_t pid; - sd_bus_message *message; - int errno_fd; - sd_event_source *event_source; - LIST_FIELDS(MachineOperation, operations); -}; - struct Machine { Manager *manager; @@ -88,10 +77,9 @@ struct Machine { int *netif; unsigned n_netif; - LIST_FIELDS(Machine, gc_queue); + LIST_HEAD(Operation, operations); - MachineOperation *operations; - unsigned n_operations; + LIST_FIELDS(Machine, gc_queue); }; Machine* machine_new(Manager *manager, MachineClass class, const char *name); @@ -109,8 +97,6 @@ void machine_release_unit(Machine *m); MachineState machine_get_state(Machine *u); -MachineOperation *machine_operation_unref(MachineOperation *o); - const char* machine_class_to_string(MachineClass t) _const_; MachineClass machine_class_from_string(const char *s) _pure_; diff --git a/src/machine/machinectl.c b/src/machine/machinectl.c index b03198bbf1..1165ab5afa 100644 --- a/src/machine/machinectl.c +++ b/src/machine/machinectl.c @@ -1076,6 +1076,7 @@ static int terminate_machine(int argc, char *argv[], void *userdata) { static int copy_files(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; _cleanup_free_ char *abs_host_path = NULL; char *dest, *host_path, *container_path; sd_bus *bus = userdata; @@ -1099,19 +1100,28 @@ static int copy_files(int argc, char *argv[], void *userdata) { host_path = abs_host_path; } - r = sd_bus_call_method( + r = sd_bus_message_new_method_call( bus, + &m, "org.freedesktop.machine1", "/org/freedesktop/machine1", "org.freedesktop.machine1.Manager", - copy_from ? "CopyFromMachine" : "CopyToMachine", - &error, - NULL, + copy_from ? "CopyFromMachine" : "CopyToMachine"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append( + m, "sss", argv[1], copy_from ? container_path : host_path, copy_from ? host_path : container_path); if (r < 0) + return bus_log_create_error(r); + + /* This is a slow operation, hence turn off any method call timeouts */ + r = sd_bus_call(bus, m, USEC_INFINITY, &error, NULL); + if (r < 0) return log_error_errno(r, "Failed to copy: %s", bus_error_message(&error, r)); return 0; @@ -1393,7 +1403,6 @@ static int shell_machine(int argc, char *argv[], void *userdata) { } static int remove_image(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = userdata; int r, i; @@ -1402,19 +1411,27 @@ static int remove_image(int argc, char *argv[], void *userdata) { polkit_agent_open_if_enabled(); for (i = 1; i < argc; i++) { - r = sd_bus_call_method( + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + + r = sd_bus_message_new_method_call( bus, + &m, "org.freedesktop.machine1", "/org/freedesktop/machine1", "org.freedesktop.machine1.Manager", - "RemoveImage", - &error, - NULL, - "s", argv[i]); - if (r < 0) { - log_error("Could not remove image: %s", bus_error_message(&error, -r)); - return r; - } + "RemoveImage"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(m, "s", argv[i]); + if (r < 0) + return bus_log_create_error(r); + + /* This is a slow operation, hence turn off any method call timeouts */ + r = sd_bus_call(bus, m, USEC_INFINITY, &error, NULL); + if (r < 0) + return log_error_errno(r, "Could not remove image: %s", bus_error_message(&error, r)); } return 0; @@ -1446,24 +1463,30 @@ static int rename_image(int argc, char *argv[], void *userdata) { static int clone_image(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; sd_bus *bus = userdata; int r; polkit_agent_open_if_enabled(); - r = sd_bus_call_method( + r = sd_bus_message_new_method_call( bus, + &m, "org.freedesktop.machine1", "/org/freedesktop/machine1", "org.freedesktop.machine1.Manager", - "CloneImage", - &error, - NULL, - "ssb", argv[1], argv[2], arg_read_only); - if (r < 0) { - log_error("Could not clone image: %s", bus_error_message(&error, -r)); - return r; - } + "CloneImage"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(m, "ssb", argv[1], argv[2], arg_read_only); + if (r < 0) + return bus_log_create_error(r); + + /* This is a slow operation, hence turn off any method call timeouts */ + r = sd_bus_call(bus, m, USEC_INFINITY, &error, NULL); + if (r < 0) + return log_error_errno(r, "Could not clone image: %s", bus_error_message(&error, r)); return 0; } @@ -2516,14 +2539,9 @@ static int parse_argv(int argc, char *argv[]) { assert(argv); for (;;) { - const char *option_string; - - if (reorder) - option_string = "hp:als:H:M:qn:o:"; - else - option_string = "+hp:als:H:M:qn:o:"; + const char * const option_string = "+hp:als:H:M:qn:o:"; - c = getopt_long(argc, argv, option_string, options, NULL); + c = getopt_long(argc, argv, option_string + reorder, options, NULL); if (c < 0) { /* We generally are fine with the fact that getopt_long() reorders the command line, and looks * for switches after the main verb. However, for "shell" we really don't want that, since we diff --git a/src/machine/machined-dbus.c b/src/machine/machined-dbus.c index c9639c3cf2..31efa3695b 100644 --- a/src/machine/machined-dbus.c +++ b/src/machine/machined-dbus.c @@ -706,6 +706,26 @@ static int method_copy_machine(sd_bus_message *message, void *userdata, sd_bus_e return bus_machine_method_copy(message, machine, error); } +static int method_open_machine_root_directory(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + Machine *machine; + const char *name; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return r; + + machine = hashmap_get(m->machines, name); + if (!machine) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); + + return bus_machine_method_open_root_directory(message, machine, error); +} + static int method_remove_image(sd_bus_message *message, void *userdata, sd_bus_error *error) { _cleanup_(image_unrefp) Image* i = NULL; const char *name; @@ -1225,6 +1245,7 @@ const sd_bus_vtable manager_vtable[] = { SD_BUS_METHOD("BindMountMachine", "sssbb", NULL, method_bind_mount_machine, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("CopyFromMachine", "sss", NULL, method_copy_machine, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("CopyToMachine", "sss", NULL, method_copy_machine, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("OpenMachineRootDirectory", "s", "h", method_open_machine_root_directory, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("RemoveImage", "s", NULL, method_remove_image, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("RenameImage", "ss", NULL, method_rename_image, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("CloneImage", "ssb", NULL, method_clone_image, SD_BUS_VTABLE_UNPRIVILEGED), diff --git a/src/machine/machined.c b/src/machine/machined.c index f2c1966a6b..f7ceb5e603 100644 --- a/src/machine/machined.c +++ b/src/machine/machined.c @@ -70,6 +70,11 @@ void manager_free(Manager *m) { assert(m); + while (m->operations) + operation_free(m->operations); + + assert(m->n_operations == 0); + while ((machine = hashmap_first(m->machines))) machine_free(machine); @@ -336,6 +341,9 @@ int manager_startup(Manager *m) { static bool check_idle(void *userdata) { Manager *m = userdata; + if (m->operations) + return false; + manager_gc(m, true); return hashmap_isempty(m->machines); diff --git a/src/machine/machined.h b/src/machine/machined.h index e7d7dfdceb..7b9b148044 100644 --- a/src/machine/machined.h +++ b/src/machine/machined.h @@ -32,6 +32,7 @@ typedef struct Manager Manager; #include "image-dbus.h" #include "machine-dbus.h" #include "machine.h" +#include "operation.h" struct Manager { sd_event *event; @@ -49,6 +50,9 @@ struct Manager { LIST_HEAD(Machine, machine_gc_queue); Machine *host_machine; + + LIST_HEAD(Operation, operations); + unsigned n_operations; }; Manager *manager_new(void); diff --git a/src/machine/operation.c b/src/machine/operation.c new file mode 100644 index 0000000000..e6ddc41a55 --- /dev/null +++ b/src/machine/operation.c @@ -0,0 +1,131 @@ +/*** + This file is part of systemd. + + Copyright 2016 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "alloc-util.h" +#include "fd-util.h" +#include "operation.h" +#include "process-util.h" + +static int operation_done(sd_event_source *s, const siginfo_t *si, void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + Operation *o = userdata; + int r; + + assert(o); + assert(si); + + log_debug("Operating " PID_FMT " is now complete with with code=%s status=%i", + o->pid, + sigchld_code_to_string(si->si_code), si->si_status); + + o->pid = 0; + + if (si->si_code != CLD_EXITED) { + r = sd_bus_error_setf(&error, SD_BUS_ERROR_FAILED, "Child died abnormally."); + goto fail; + } + + if (si->si_status != EXIT_SUCCESS) { + if (read(o->errno_fd, &r, sizeof(r)) == sizeof(r)) + r = sd_bus_error_set_errnof(&error, r, "%m"); + else + r = sd_bus_error_setf(&error, SD_BUS_ERROR_FAILED, "Child failed."); + + goto fail; + } + + r = sd_bus_reply_method_return(o->message, NULL); + if (r < 0) + log_error_errno(r, "Failed to reply to message: %m"); + + operation_free(o); + return 0; + +fail: + r = sd_bus_reply_method_error(o->message, &error); + if (r < 0) + log_error_errno(r, "Failed to reply to message: %m"); + + operation_free(o); + return 0; +} + +int operation_new(Manager *manager, Machine *machine, pid_t child, sd_bus_message *message, int errno_fd) { + Operation *o; + int r; + + assert(manager); + assert(child > 1); + assert(message); + assert(errno_fd >= 0); + + o = new0(Operation, 1); + if (!o) + return -ENOMEM; + + r = sd_event_add_child(manager->event, &o->event_source, child, WEXITED, operation_done, o); + if (r < 0) { + free(o); + return r; + } + + o->pid = child; + o->message = sd_bus_message_ref(message); + o->errno_fd = errno_fd; + + LIST_PREPEND(operations, manager->operations, o); + manager->n_operations++; + o->manager = manager; + + if (machine) { + LIST_PREPEND(operations_by_machine, machine->operations, o); + o->machine = machine; + } + + log_debug("Started new operation " PID_FMT ".", child); + + /* At this point we took ownership of both the child and the errno file descriptor! */ + + return 0; +} + +Operation *operation_free(Operation *o) { + if (!o) + return NULL; + + sd_event_source_unref(o->event_source); + + safe_close(o->errno_fd); + + if (o->pid > 1) + (void) sigkill_wait(o->pid); + + sd_bus_message_unref(o->message); + + if (o->manager) { + LIST_REMOVE(operations, o->manager->operations, o); + o->manager->n_operations--; + } + + if (o->machine) + LIST_REMOVE(operations_by_machine, o->machine->operations, o); + + free(o); + return NULL; +} diff --git a/src/machine/operation.h b/src/machine/operation.h new file mode 100644 index 0000000000..7ca47bc3af --- /dev/null +++ b/src/machine/operation.h @@ -0,0 +1,47 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2016 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <sys/types.h> + +#include "sd-bus.h" +#include "sd-event.h" + +#include "list.h" + +typedef struct Operation Operation; + +#include "machined.h" + +#define OPERATIONS_MAX 64 + +struct Operation { + Manager *manager; + Machine *machine; + pid_t pid; + sd_bus_message *message; + int errno_fd; + sd_event_source *event_source; + LIST_FIELDS(Operation, operations); + LIST_FIELDS(Operation, operations_by_machine); +}; + +int operation_new(Manager *manager, Machine *machine, pid_t child, sd_bus_message *message, int errno_fd); +Operation *operation_free(Operation *o); diff --git a/src/network/networkd-address.c b/src/network/networkd-address.c index 7f9a7268cc..429319da6b 100644 --- a/src/network/networkd-address.c +++ b/src/network/networkd-address.c @@ -67,16 +67,15 @@ int address_new_static(Network *network, unsigned section, Address **ret) { if (r < 0) return r; - address->network = network; - - LIST_APPEND(addresses, network->static_addresses, address); - if (section) { address->section = section; hashmap_put(network->addresses_by_section, UINT_TO_PTR(address->section), address); } + address->network = network; + LIST_APPEND(addresses, network->static_addresses, address); + *ret = address; address = NULL; diff --git a/src/network/networkd-conf.c b/src/network/networkd-conf.c index 73a8d16b58..70f0121d6d 100644 --- a/src/network/networkd-conf.c +++ b/src/network/networkd-conf.c @@ -31,7 +31,7 @@ int manager_parse_config_file(Manager *m) { return config_parse_many(PKGSYSCONFDIR "/networkd.conf", CONF_PATHS_NULSTR("systemd/networkd.conf.d"), - "DUID\0", + "DHCP\0", config_item_perf_lookup, networkd_gperf_lookup, false, m); } @@ -57,7 +57,8 @@ int config_parse_duid_rawdata( const char *rvalue, void *data, void *userdata) { - int r, n1, n2, byte; + + int r; char *cbyte; const char *pduid = rvalue; Manager *m = userdata; @@ -72,71 +73,78 @@ int config_parse_duid_rawdata( assert(rvalue); assert(userdata); - duidtype = (ltype == DUID_CONFIG_SOURCE_GLOBAL) ? m->duid_type - : n->duid_type; + duidtype = (ltype == DUID_CONFIG_SOURCE_GLOBAL) ? m->duid_type : n->duid_type; if (duidtype == _DUID_TYPE_INVALID) duidtype = DUID_TYPE_RAW; switch (duidtype) { + case DUID_TYPE_LLT: /* RawData contains DUID-LLT link-layer address (offset 6) */ duid_start_offset = 6; break; + case DUID_TYPE_EN: /* RawData contains DUID-EN identifier (offset 4) */ duid_start_offset = 4; break; + case DUID_TYPE_LL: /* RawData contains DUID-LL link-layer address (offset 2) */ duid_start_offset = 2; break; + case DUID_TYPE_UUID: /* RawData specifies UUID (offset 0) - fall thru */ + case DUID_TYPE_RAW: /* First two bytes of RawData is DUID Type - fall thru */ + default: break; } if (duidtype != DUID_TYPE_RAW) - dhcp_duid_type = (uint16_t)duidtype; + dhcp_duid_type = (uint16_t) duidtype; /* RawData contains DUID in format " NN:NN:NN... " */ for (;;) { + int n1, n2; + uint32_t byte; + r = extract_first_word(&pduid, &cbyte, ":", 0); if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, - "Failed to read DUID, ignoring assignment: %s.", rvalue); - goto exit; + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to read DUID, ignoring assignment: %s.", rvalue); + return 0; } if (r == 0) break; - if ((duid_start_offset + dhcp_duid_len) >= MAX_DUID_LEN) { - log_syntax(unit, LOG_ERR, filename, line, 0, - "Max DUID length exceeded, ignoring assignment: %s.", rvalue); - goto exit; + if (duid_start_offset + dhcp_duid_len >= MAX_DUID_LEN) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Max DUID length exceeded, ignoring assignment: %s.", rvalue); + return 0; } len = strlen(cbyte); - if ((len == 0) || (len > 2)) { - log_syntax(unit, LOG_ERR, filename, line, 0, - "Invalid length - DUID byte: %s, ignoring assignment: %s.", cbyte, rvalue); - goto exit; + if (len != 1 && len != 2) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid length - DUID byte: %s, ignoring assignment: %s.", cbyte, rvalue); + return 0; } - n2 = 0; n1 = unhexchar(cbyte[0]); if (len == 2) n2 = unhexchar(cbyte[1]); - if ((n1 < 0) || (n2 < 0)) { - log_syntax(unit, LOG_ERR, filename, line, 0, - "Invalid DUID byte: %s. Ignoring assignment: %s.", cbyte, rvalue); - goto exit; + else + n2 = 0; + + if (n1 < 0 || n2 < 0) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid DUID byte: %s. Ignoring assignment: %s.", cbyte, rvalue); + return 0; } - byte = (n1 << (4 * (len-1))) | n2; + + byte = ((uint8_t) n1 << (4 * (len-1))) | (uint8_t) n2; /* If DUID_TYPE_RAW, first two bytes hold DHCP DUID type code */ - if ((duidtype == DUID_TYPE_RAW) && (count < 2)) { + if (duidtype == DUID_TYPE_RAW && count < 2) { dhcp_duid_type |= (byte << (8 * (1 - count))); count++; continue; @@ -159,6 +167,5 @@ int config_parse_duid_rawdata( memcpy(&n->dhcp_duid[duid_start_offset], dhcp_duid, dhcp_duid_len); } -exit: return 0; } diff --git a/src/network/networkd-gperf.gperf b/src/network/networkd-gperf.gperf index 0625fb335b..afc71b4cb8 100644 --- a/src/network/networkd-gperf.gperf +++ b/src/network/networkd-gperf.gperf @@ -14,5 +14,5 @@ struct ConfigPerfItem; %struct-type %includes %% -DUID.Type, config_parse_duid_type, 0, offsetof(Manager, duid_type) -DUID.RawData, config_parse_duid_rawdata, DUID_CONFIG_SOURCE_GLOBAL, offsetof(Manager, dhcp_duid) +DHCP.DUIDType, config_parse_duid_type, 0, offsetof(Manager, duid_type) +DHCP.DUIDRawData, config_parse_duid_rawdata, DUID_CONFIG_SOURCE_GLOBAL, offsetof(Manager, dhcp_duid) diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index 5101d553d5..5fc513bfda 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -576,8 +576,6 @@ static void link_set_state(Link *link, LinkState state) { link->state = state; link_send_changed(link, "AdministrativeState", NULL); - - return; } static void link_enter_unmanaged(Link *link) { @@ -1458,7 +1456,7 @@ static int link_acquire_ipv6_conf(Link *link) { return 0; } -static int link_acquire_conf(Link *link) { +static int link_acquire_ipv4_conf(Link *link) { int r; assert(link); @@ -1486,6 +1484,24 @@ static int link_acquire_conf(Link *link) { return log_link_warning_errno(link, r, "Could not acquire DHCPv4 lease: %m"); } + return 0; +} + +static int link_acquire_conf(Link *link) { + int r; + + assert(link); + + r = link_acquire_ipv4_conf(link); + if (r < 0) + return r; + + if (in_addr_is_null(AF_INET6, (const union in_addr_union*) &link->ipv6ll_address) == 0) { + r = link_acquire_ipv6_conf(link); + if (r < 0) + return r; + } + if (link_lldp_tx_enabled(link)) { r = link_lldp_tx_start(link); if (r < 0) @@ -2351,12 +2367,6 @@ static int link_configure(Link *link) { r = link_acquire_conf(link); if (r < 0) return r; - - if (in_addr_is_null(AF_INET6, (const union in_addr_union*) &link->ipv6ll_address) == 0) { - r = link_acquire_ipv6_conf(link); - if (r < 0) - return r; - } } return link_enter_join_netdev(link); @@ -2739,6 +2749,10 @@ static int link_carrier_gained(Link *link) { link_enter_failed(link); return r; } + + r = link_enter_set_addresses(link); + if (r < 0) + return r; } r = link_handle_bound_by_list(link); diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index 1da99cd5bc..654d6a0316 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -28,8 +28,6 @@ Match.Architecture, config_parse_net_condition, Link.MACAddress, config_parse_hwaddr, 0, offsetof(Network, mac) Link.MTUBytes, config_parse_iec_size, 0, offsetof(Network, mtu) Link.IAID, config_parse_iaid, 0, offsetof(Network, iaid) -DUID.Type, config_parse_duid_type, 0, offsetof(Network, duid_type) -DUID.RawData, config_parse_duid_rawdata, DUID_CONFIG_SOURCE_NETWORK, offsetof(Network, dhcp_duid) Network.Description, config_parse_string, 0, offsetof(Network, description) Network.Bridge, config_parse_netdev, 0, offsetof(Network, bridge) Network.Bond, config_parse_netdev, 0, offsetof(Network, bond) @@ -85,6 +83,8 @@ DHCP.Hostname, config_parse_hostname, DHCP.RequestBroadcast, config_parse_bool, 0, offsetof(Network, dhcp_broadcast) DHCP.CriticalConnection, config_parse_bool, 0, offsetof(Network, dhcp_critical) DHCP.VendorClassIdentifier, config_parse_string, 0, offsetof(Network, dhcp_vendor_class_identifier) +DHCP.DUIDType, config_parse_duid_type, 0, offsetof(Network, duid_type) +DHCP.DUIDRawData, config_parse_duid_rawdata, DUID_CONFIG_SOURCE_NETWORK, offsetof(Network, dhcp_duid) DHCP.RouteMetric, config_parse_unsigned, 0, offsetof(Network, dhcp_route_metric) DHCP.UseTimezone, config_parse_bool, 0, offsetof(Network, dhcp_use_timezone) DHCPServer.MaxLeaseTimeSec, config_parse_sec, 0, offsetof(Network, dhcp_server_max_lease_time_usec) diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index 07f8fb028f..2ebcdfa744 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -137,7 +137,6 @@ static int network_load_one(Manager *manager, const char *filename) { r = config_parse(NULL, filename, file, "Match\0" "Link\0" - "DUID\0" "Network\0" "Address\0" "Route\0" diff --git a/src/network/networkd-route.c b/src/network/networkd-route.c index ab9b777d9a..bda2707e6d 100644 --- a/src/network/networkd-route.c +++ b/src/network/networkd-route.c @@ -68,15 +68,15 @@ int route_new_static(Network *network, unsigned section, Route **ret) { route->protocol = RTPROT_STATIC; if (section) { + route->section = section; + r = hashmap_put(network->routes_by_section, UINT_TO_PTR(route->section), route); if (r < 0) return r; - - route->section = section; } - LIST_PREPEND(routes, network->static_routes, route); route->network = network; + LIST_PREPEND(routes, network->static_routes, route); *ret = route; route = NULL; diff --git a/src/nspawn/nspawn-gperf.gperf b/src/nspawn/nspawn-gperf.gperf index 116655cdd2..34e1310e29 100644 --- a/src/nspawn/nspawn-gperf.gperf +++ b/src/nspawn/nspawn-gperf.gperf @@ -16,7 +16,7 @@ struct ConfigPerfItem; %includes %% Exec.Boot, config_parse_boot, 0, 0 -Exec.ProcessTwo, config_parse_pid2, 0, 0, +Exec.ProcessTwo, config_parse_pid2, 0, 0 Exec.Parameters, config_parse_strv, 0, offsetof(Settings, parameters) Exec.Environment, config_parse_strv, 0, offsetof(Settings, environment) Exec.User, config_parse_string, 0, offsetof(Settings, user) @@ -26,11 +26,13 @@ Exec.KillSignal, config_parse_signal, 0, offsetof(Settings, Exec.Personality, config_parse_personality, 0, offsetof(Settings, personality) Exec.MachineID, config_parse_id128, 0, offsetof(Settings, machine_id) Exec.WorkingDirectory, config_parse_path, 0, offsetof(Settings, working_directory) +Exec.PrivateUsers, config_parse_private_users, 0, 0 Files.ReadOnly, config_parse_tristate, 0, offsetof(Settings, read_only) Files.Volatile, config_parse_volatile_mode, 0, offsetof(Settings, volatile_mode) Files.Bind, config_parse_bind, 0, 0 Files.BindReadOnly, config_parse_bind, 1, 0 Files.TemporaryFileSystem, config_parse_tmpfs, 0, 0 +Files.PrivateUsersChown, config_parse_tristate, 0, offsetof(Settings, userns_chown) Network.Private, config_parse_tristate, 0, offsetof(Settings, private_network) Network.Interface, config_parse_strv, 0, offsetof(Settings, network_interfaces) Network.MACVLAN, config_parse_strv, 0, offsetof(Settings, network_macvlan) diff --git a/src/nspawn/nspawn-patch-uid.c b/src/nspawn/nspawn-patch-uid.c new file mode 100644 index 0000000000..c7382d412d --- /dev/null +++ b/src/nspawn/nspawn-patch-uid.c @@ -0,0 +1,469 @@ +/*** + This file is part of systemd. + + Copyright 2016 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <fcntl.h> +#include <linux/magic.h> +#ifdef HAVE_ACL +#include <sys/acl.h> +#endif +#include <sys/stat.h> +#include <sys/vfs.h> +#include <unistd.h> + +#include "acl-util.h" +#include "dirent-util.h" +#include "fd-util.h" +#include "missing.h" +#include "nspawn-patch-uid.h" +#include "stat-util.h" +#include "stdio-util.h" +#include "string-util.h" +#include "strv.h" +#include "user-util.h" + +#ifdef HAVE_ACL + +static int get_acl(int fd, const char *name, acl_type_t type, acl_t *ret) { + char procfs_path[strlen("/proc/self/fd/") + DECIMAL_STR_MAX(int) + 1]; + acl_t acl; + + assert(fd >= 0); + assert(ret); + + if (name) { + _cleanup_close_ int child_fd = -1; + + child_fd = openat(fd, name, O_PATH|O_CLOEXEC|O_NOFOLLOW); + if (child_fd < 0) + return -errno; + + xsprintf(procfs_path, "/proc/self/fd/%i", child_fd); + acl = acl_get_file(procfs_path, type); + } else if (type == ACL_TYPE_ACCESS) + acl = acl_get_fd(fd); + else { + xsprintf(procfs_path, "/proc/self/fd/%i", fd); + acl = acl_get_file(procfs_path, type); + } + if (!acl) + return -errno; + + *ret = acl; + return 0; +} + +static int set_acl(int fd, const char *name, acl_type_t type, acl_t acl) { + char procfs_path[strlen("/proc/self/fd/") + DECIMAL_STR_MAX(int) + 1]; + int r; + + assert(fd >= 0); + assert(acl); + + if (name) { + _cleanup_close_ int child_fd = -1; + + child_fd = openat(fd, name, O_PATH|O_CLOEXEC|O_NOFOLLOW); + if (child_fd < 0) + return -errno; + + xsprintf(procfs_path, "/proc/self/fd/%i", child_fd); + r = acl_set_file(procfs_path, type, acl); + } else if (type == ACL_TYPE_ACCESS) + r = acl_set_fd(fd, acl); + else { + xsprintf(procfs_path, "/proc/self/fd/%i", fd); + r = acl_set_file(procfs_path, type, acl); + } + if (r < 0) + return -errno; + + return 0; +} + +static int shift_acl(acl_t acl, uid_t shift, acl_t *ret) { + _cleanup_(acl_freep) acl_t copy = NULL; + acl_entry_t i; + int r; + + assert(acl); + assert(ret); + + r = acl_get_entry(acl, ACL_FIRST_ENTRY, &i); + if (r < 0) + return -errno; + while (r > 0) { + uid_t *old_uid, new_uid; + bool modify = false; + acl_tag_t tag; + + if (acl_get_tag_type(i, &tag) < 0) + return -errno; + + if (IN_SET(tag, ACL_USER, ACL_GROUP)) { + + /* We don't distuingish here between uid_t and gid_t, let's make sure the compiler checks that + * this is actually OK */ + assert_cc(sizeof(uid_t) == sizeof(gid_t)); + + old_uid = acl_get_qualifier(i); + if (!old_uid) + return -errno; + + new_uid = shift | (*old_uid & UINT32_C(0xFFFF)); + if (!uid_is_valid(new_uid)) + return -EINVAL; + + modify = new_uid != *old_uid; + if (modify && !copy) { + int n; + + /* There's no copy of the ACL yet? if so, let's create one, and start the loop from the + * beginning, so that we copy all entries, starting from the first, this time. */ + + n = acl_entries(acl); + if (n < 0) + return -errno; + + copy = acl_init(n); + if (!copy) + return -errno; + + /* Seek back to the beginning */ + r = acl_get_entry(acl, ACL_FIRST_ENTRY, &i); + if (r < 0) + return -errno; + continue; + } + } + + if (copy) { + acl_entry_t new_entry; + + if (acl_create_entry(©, &new_entry) < 0) + return -errno; + + if (acl_copy_entry(new_entry, i) < 0) + return -errno; + + if (modify) + if (acl_set_qualifier(new_entry, &new_uid) < 0) + return -errno; + } + + r = acl_get_entry(acl, ACL_NEXT_ENTRY, &i); + if (r < 0) + return -errno; + } + + *ret = copy; + copy = NULL; + + return !!*ret; +} + +static int patch_acls(int fd, const char *name, const struct stat *st, uid_t shift) { + _cleanup_(acl_freep) acl_t acl = NULL, shifted = NULL; + bool changed = false; + int r; + + assert(fd >= 0); + assert(st); + + /* ACLs are not supported on symlinks, there's no point in trying */ + if (S_ISLNK(st->st_mode)) + return 0; + + r = get_acl(fd, name, ACL_TYPE_ACCESS, &acl); + if (r == -EOPNOTSUPP) + return 0; + if (r < 0) + return r; + + r = shift_acl(acl, shift, &shifted); + if (r < 0) + return r; + if (r > 0) { + r = set_acl(fd, name, ACL_TYPE_ACCESS, shifted); + if (r < 0) + return r; + + changed = true; + } + + if (S_ISDIR(st->st_mode)) { + acl_free(acl); + acl_free(shifted); + + acl = shifted = NULL; + + r = get_acl(fd, name, ACL_TYPE_DEFAULT, &acl); + if (r < 0) + return r; + + r = shift_acl(acl, shift, &shifted); + if (r < 0) + return r; + if (r > 0) { + r = set_acl(fd, name, ACL_TYPE_DEFAULT, shifted); + if (r < 0) + return r; + + changed = true; + } + } + + return changed; +} + +#else + +static int patch_acls(int fd, const char *name, const struct stat *st, uid_t shift) { + return 0; +} + +#endif + +static int patch_fd(int fd, const char *name, const struct stat *st, uid_t shift) { + uid_t new_uid; + gid_t new_gid; + bool changed = false; + int r; + + assert(fd >= 0); + assert(st); + + new_uid = shift | (st->st_uid & UINT32_C(0xFFFF)); + new_gid = (gid_t) shift | (st->st_gid & UINT32_C(0xFFFF)); + + if (!uid_is_valid(new_uid) || !gid_is_valid(new_gid)) + return -EINVAL; + + if (st->st_uid != new_uid || st->st_gid != new_gid) { + if (name) + r = fchownat(fd, name, new_uid, new_gid, AT_SYMLINK_NOFOLLOW); + else + r = fchown(fd, new_uid, new_gid); + if (r < 0) + return -errno; + + /* The Linux kernel alters the mode in some cases of chown(). Let's undo this. */ + if (name && !S_ISLNK(st->st_mode)) + r = fchmodat(fd, name, st->st_mode, 0); + else + r = fchmod(fd, st->st_mode); + if (r < 0) + return -errno; + + changed = true; + } + + r = patch_acls(fd, name, st, shift); + if (r < 0) + return r; + + return r > 0 || changed; +} + +static int is_procfs_sysfs_or_suchlike(int fd) { + struct statfs sfs; + + assert(fd >= 0); + + if (fstatfs(fd, &sfs) < 0) + return -errno; + + return F_TYPE_EQUAL(sfs.f_type, BINFMTFS_MAGIC) || + F_TYPE_EQUAL(sfs.f_type, CGROUP_SUPER_MAGIC) || + F_TYPE_EQUAL(sfs.f_type, CGROUP2_SUPER_MAGIC) || + F_TYPE_EQUAL(sfs.f_type, DEBUGFS_MAGIC) || + F_TYPE_EQUAL(sfs.f_type, DEVPTS_SUPER_MAGIC) || + F_TYPE_EQUAL(sfs.f_type, EFIVARFS_MAGIC) || + F_TYPE_EQUAL(sfs.f_type, HUGETLBFS_MAGIC) || + F_TYPE_EQUAL(sfs.f_type, MQUEUE_MAGIC) || + F_TYPE_EQUAL(sfs.f_type, PROC_SUPER_MAGIC) || + F_TYPE_EQUAL(sfs.f_type, PSTOREFS_MAGIC) || + F_TYPE_EQUAL(sfs.f_type, SELINUX_MAGIC) || + F_TYPE_EQUAL(sfs.f_type, SMACK_MAGIC) || + F_TYPE_EQUAL(sfs.f_type, SYSFS_MAGIC); +} + +static int recurse_fd(int fd, bool donate_fd, const struct stat *st, uid_t shift, bool is_toplevel) { + bool changed = false; + int r; + + assert(fd >= 0); + + /* We generally want to permit crossing of mount boundaries when patching the UIDs/GIDs. However, we + * probably shouldn't do this for /proc and /sys if that is already mounted into place. Hence, let's + * stop the recursion when we hit a procfs or sysfs file system. */ + r = is_procfs_sysfs_or_suchlike(fd); + if (r < 0) + goto finish; + if (r > 0) { + r = 0; /* don't recurse */ + goto finish; + } + + r = patch_fd(fd, NULL, st, shift); + if (r == -EROFS) { + _cleanup_free_ char *name = NULL; + + if (!is_toplevel) { + /* When we hit a ready-only subtree we simply skip it, but log about it. */ + (void) fd_get_path(fd, &name); + log_debug("Skippping read-only file or directory %s.", strna(name)); + r = 0; + } + + goto finish; + } + if (r < 0) + goto finish; + + if (S_ISDIR(st->st_mode)) { + _cleanup_closedir_ DIR *d = NULL; + struct dirent *de; + + if (!donate_fd) { + int copy; + + copy = fcntl(fd, F_DUPFD_CLOEXEC, 3); + if (copy < 0) { + r = -errno; + goto finish; + } + + fd = copy; + donate_fd = true; + } + + d = fdopendir(fd); + if (!d) { + r = -errno; + goto finish; + } + fd = -1; + + FOREACH_DIRENT_ALL(de, d, r = -errno; goto finish) { + struct stat fst; + + if (STR_IN_SET(de->d_name, ".", "..")) + continue; + + if (fstatat(dirfd(d), de->d_name, &fst, AT_SYMLINK_NOFOLLOW) < 0) { + r = -errno; + goto finish; + } + + if (S_ISDIR(fst.st_mode)) { + int subdir_fd; + + subdir_fd = openat(dirfd(d), de->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME); + if (subdir_fd < 0) { + r = -errno; + goto finish; + + } + + r = recurse_fd(subdir_fd, true, &fst, shift, false); + if (r < 0) + goto finish; + if (r > 0) + changed = true; + + } else { + r = patch_fd(dirfd(d), de->d_name, &fst, shift); + if (r < 0) + goto finish; + if (r > 0) + changed = true; + } + } + } + + r = changed; + +finish: + if (donate_fd) + safe_close(fd); + + return r; +} + +static int fd_patch_uid_internal(int fd, bool donate_fd, uid_t shift, uid_t range) { + struct stat st; + int r; + + assert(fd >= 0); + + /* Recursively adjusts the UID/GIDs of all files of a directory tree. This is used to automatically fix up an + * OS tree to the used user namespace UID range. Note that this automatic adjustment only works for UID ranges + * following the concept that the upper 16bit of a UID identify the container, and the lower 16bit are the actual + * UID within the container. */ + + if ((shift & 0xFFFF) != 0) { + /* We only support containers where the shift starts at a 2^16 boundary */ + r = -EOPNOTSUPP; + goto finish; + } + + if (range != 0x10000) { + /* We only support containers with 16bit UID ranges for the patching logic */ + r = -EOPNOTSUPP; + goto finish; + } + + if (fstat(fd, &st) < 0) { + r = -errno; + goto finish; + } + + if ((uint32_t) st.st_uid >> 16 != (uint32_t) st.st_gid >> 16) { + /* We only support containers where the uid/gid container ID match */ + r = -EBADE; + goto finish; + } + + /* Try to detect if the range is already right. Of course, this a pretty drastic optimization, as we assume + * that if the top-level dir has the right upper 16bit assigned, then everything below will have too... */ + if (((uint32_t) (st.st_uid ^ shift) >> 16) == 0) + return 0; + + return recurse_fd(fd, donate_fd, &st, shift, true); + +finish: + if (donate_fd) + safe_close(fd); + + return r; +} + +int fd_patch_uid(int fd, uid_t shift, uid_t range) { + return fd_patch_uid_internal(fd, false, shift, range); +} + +int path_patch_uid(const char *path, uid_t shift, uid_t range) { + int fd; + + fd = open(path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME); + if (fd < 0) + return -errno; + + return fd_patch_uid_internal(fd, true, shift, range); +} diff --git a/src/nspawn/nspawn-patch-uid.h b/src/nspawn/nspawn-patch-uid.h new file mode 100644 index 0000000000..55d0990016 --- /dev/null +++ b/src/nspawn/nspawn-patch-uid.h @@ -0,0 +1,23 @@ +/*** + This file is part of systemd. + + Copyright 2016 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <sys/types.h> + +int fd_patch_uid(int fd, uid_t shift, uid_t range); +int path_patch_uid(const char *path, uid_t shift, uid_t range); diff --git a/src/nspawn/nspawn-settings.c b/src/nspawn/nspawn-settings.c index 4fb0054698..b98a79fd09 100644 --- a/src/nspawn/nspawn-settings.c +++ b/src/nspawn/nspawn-settings.c @@ -25,7 +25,9 @@ #include "parse-util.h" #include "process-util.h" #include "strv.h" +#include "user-util.h" #include "util.h" +#include "string-util.h" int settings_load(FILE *f, const char *path, Settings **ret) { _cleanup_(settings_freep) Settings *s = NULL; @@ -40,9 +42,13 @@ int settings_load(FILE *f, const char *path, Settings **ret) { s->start_mode = _START_MODE_INVALID; s->personality = PERSONALITY_INVALID; + s->userns_mode = _USER_NAMESPACE_MODE_INVALID; + s->uid_shift = UID_INVALID; + s->uid_range = UID_INVALID; s->read_only = -1; s->volatile_mode = _VOLATILE_MODE_INVALID; + s->userns_chown = -1; s->private_network = -1; s->network_veth = -1; @@ -59,6 +65,16 @@ int settings_load(FILE *f, const char *path, Settings **ret) { if (r < 0) return r; + /* Make sure that if userns_mode is set, userns_chown is set to something appropriate, and vice versa. Either + * both fields shall be initialized or neither. */ + if (s->userns_mode == USER_NAMESPACE_PICK) + s->userns_chown = true; + else if (s->userns_mode != _USER_NAMESPACE_MODE_INVALID && s->userns_chown < 0) + s->userns_chown = false; + + if (s->userns_chown >= 0 && s->userns_mode == _USER_NAMESPACE_MODE_INVALID) + s->userns_mode = USER_NAMESPACE_NO; + *ret = s; s = NULL; @@ -392,3 +408,73 @@ conflict: log_syntax(unit, LOG_ERR, filename, line, r, "Conflicting Boot= or ProcessTwo= setting found. Ignoring."); return 0; } + +int config_parse_private_users( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Settings *settings = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + r = parse_boolean(rvalue); + if (r == 0) { + /* no: User namespacing off */ + settings->userns_mode = USER_NAMESPACE_NO; + settings->uid_shift = UID_INVALID; + settings->uid_range = UINT32_C(0x10000); + } else if (r > 0) { + /* yes: User namespacing on, UID range is read from root dir */ + settings->userns_mode = USER_NAMESPACE_FIXED; + settings->uid_shift = UID_INVALID; + settings->uid_range = UINT32_C(0x10000); + } else if (streq(rvalue, "pick")) { + /* pick: User namespacing on, UID range is picked randomly */ + settings->userns_mode = USER_NAMESPACE_PICK; + settings->uid_shift = UID_INVALID; + settings->uid_range = UINT32_C(0x10000); + } else { + const char *range, *shift; + uid_t sh, rn; + + /* anything else: User namespacing on, UID range is explicitly configured */ + + range = strchr(rvalue, ':'); + if (range) { + shift = strndupa(rvalue, range - rvalue); + range++; + + r = safe_atou32(range, &rn); + if (r < 0 || rn <= 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "UID/GID range invalid, ignoring: %s", range); + return 0; + } + } else { + shift = rvalue; + rn = UINT32_C(0x10000); + } + + r = parse_uid(shift, &sh); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "UID/GID shift invalid, ignoring: %s", range); + return 0; + } + + settings->userns_mode = USER_NAMESPACE_FIXED; + settings->uid_shift = sh; + settings->uid_range = rn; + } + + return 0; +} diff --git a/src/nspawn/nspawn-settings.h b/src/nspawn/nspawn-settings.h index a017405cd9..e12e91b886 100644 --- a/src/nspawn/nspawn-settings.h +++ b/src/nspawn/nspawn-settings.h @@ -33,6 +33,14 @@ typedef enum StartMode { _START_MODE_INVALID = -1 } StartMode; +typedef enum UserNamespaceMode { + USER_NAMESPACE_NO, + USER_NAMESPACE_FIXED, + USER_NAMESPACE_PICK, + _USER_NAMESPACE_MODE_MAX, + _USER_NAMESPACE_MODE_INVALID = -1, +} UserNamespaceMode; + typedef enum SettingsMask { SETTING_START_MODE = 1 << 0, SETTING_ENVIRONMENT = 1 << 1, @@ -47,7 +55,8 @@ typedef enum SettingsMask { SETTING_VOLATILE_MODE = 1 << 10, SETTING_CUSTOM_MOUNTS = 1 << 11, SETTING_WORKING_DIRECTORY = 1 << 12, - _SETTINGS_MASK_ALL = (1 << 13) -1 + SETTING_USERNS = 1 << 13, + _SETTINGS_MASK_ALL = (1 << 14) -1 } SettingsMask; typedef struct Settings { @@ -62,12 +71,15 @@ typedef struct Settings { unsigned long personality; sd_id128_t machine_id; char *working_directory; + UserNamespaceMode userns_mode; + uid_t uid_shift, uid_range; /* [Image] */ int read_only; VolatileMode volatile_mode; CustomMount *custom_mounts; unsigned n_custom_mounts; + int userns_chown; /* [Network] */ int private_network; @@ -99,3 +111,4 @@ int config_parse_tmpfs(const char *unit, const char *filename, unsigned line, co int config_parse_veth_extra(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_boot(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_pid2(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_private_users(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index d687df8a09..3fc6cc955c 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -22,7 +22,9 @@ #endif #include <errno.h> #include <getopt.h> +#include <grp.h> #include <linux/loop.h> +#include <pwd.h> #include <sched.h> #ifdef HAVE_SECCOMP #include <seccomp.h> @@ -75,6 +77,7 @@ #include "nspawn-expose-ports.h" #include "nspawn-mount.h" #include "nspawn-network.h" +#include "nspawn-patch-uid.h" #include "nspawn-register.h" #include "nspawn-settings.h" #include "nspawn-setuid.h" @@ -101,6 +104,11 @@ #include "user-util.h" #include "util.h" +/* Note that devpts's gid= parameter parses GIDs as signed values, hence we stay away from the upper half of the 32bit + * UID range here */ +#define UID_SHIFT_PICK_MIN ((uid_t) UINT32_C(0x00080000)) +#define UID_SHIFT_PICK_MAX ((uid_t) UINT32_C(0x6FFF0000)) + typedef enum ContainerStatus { CONTAINER_TERMINATED, CONTAINER_REBOOTED @@ -173,8 +181,9 @@ static char *arg_image = NULL; static VolatileMode arg_volatile_mode = VOLATILE_NO; static ExposePort *arg_expose_ports = NULL; static char **arg_property = NULL; +static UserNamespaceMode arg_userns_mode = USER_NAMESPACE_NO; static uid_t arg_uid_shift = UID_INVALID, arg_uid_range = 0x10000U; -static bool arg_userns = false; +static bool arg_userns_chown = false; static int arg_kill_signal = 0; static bool arg_unified_cgroup_hierarchy = false; static SettingsMask arg_settings_mask = 0; @@ -202,8 +211,10 @@ static void help(void) { " --uuid=UUID Set a specific machine UUID for the container\n" " -S --slice=SLICE Place the container in the specified slice\n" " --property=NAME=VALUE Set scope unit property\n" + " -U --private-users=pick Run within user namespace, pick UID/GID range automatically\n" " --private-users[=UIDBASE[:NUIDS]]\n" - " Run within user namespace\n" + " Run within user namespace, user configured UID/GID range\n" + " --private-user-chown Adjust OS tree file ownership for private UID/GID range\n" " --private-network Disable network in container\n" " --network-interface=INTERFACE\n" " Assign an existing network interface to the\n" @@ -272,9 +283,15 @@ static int custom_mounts_prepare(void) { for (i = 0; i < arg_n_custom_mounts; i++) { CustomMount *m = &arg_custom_mounts[i]; - if (arg_userns && arg_uid_shift == UID_INVALID && path_equal(m->destination, "/")) { - log_error("--private-users with automatic UID shift may not be combined with custom root mounts."); - return -EINVAL; + if (path_equal(m->destination, "/") && arg_userns_mode != USER_NAMESPACE_NO) { + + if (arg_userns_chown) { + log_error("--private-users-chown may not be combined with custom root mounts."); + return -EINVAL; + } else if (arg_uid_shift == UID_INVALID) { + log_error("--private-users with automatic UID shift may not be combined with custom root mounts."); + return -EINVAL; + } } if (m->type != CUSTOM_MOUNT_OVERLAY) @@ -349,6 +366,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_KILL_SIGNAL, ARG_SETTINGS, ARG_CHDIR, + ARG_PRIVATE_USERS_CHOWN, }; static const struct option options[] = { @@ -392,6 +410,7 @@ static int parse_argv(int argc, char *argv[]) { { "port", required_argument, NULL, 'p' }, { "property", required_argument, NULL, ARG_PROPERTY }, { "private-users", optional_argument, NULL, ARG_PRIVATE_USERS }, + { "private-users-chown", optional_argument, NULL, ARG_PRIVATE_USERS_CHOWN}, { "kill-signal", required_argument, NULL, ARG_KILL_SIGNAL }, { "settings", required_argument, NULL, ARG_SETTINGS }, { "chdir", required_argument, NULL, ARG_CHDIR }, @@ -406,7 +425,7 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "+hD:u:abL:M:jS:Z:qi:xp:n", options, NULL)) >= 0) + while ((c = getopt_long(argc, argv, "+hD:u:abL:M:jS:Z:qi:xp:nU", options, NULL)) >= 0) switch (c) { @@ -797,10 +816,29 @@ static int parse_argv(int argc, char *argv[]) { break; case ARG_PRIVATE_USERS: - if (optarg) { + + r = optarg ? parse_boolean(optarg) : 1; + if (r == 0) { + /* no: User namespacing off */ + arg_userns_mode = USER_NAMESPACE_NO; + arg_uid_shift = UID_INVALID; + arg_uid_range = UINT32_C(0x10000); + } else if (r > 0) { + /* yes: User namespacing on, UID range is read from root dir */ + arg_userns_mode = USER_NAMESPACE_FIXED; + arg_uid_shift = UID_INVALID; + arg_uid_range = UINT32_C(0x10000); + } else if (streq(optarg, "pick")) { + /* pick: User namespacing on, UID range is picked randomly */ + arg_userns_mode = USER_NAMESPACE_PICK; + arg_uid_shift = UID_INVALID; + arg_uid_range = UINT32_C(0x10000); + } else { _cleanup_free_ char *buffer = NULL; const char *range, *shift; + /* anything else: User namespacing on, UID range is explicitly configured */ + range = strchr(optarg, ':'); if (range) { buffer = strndup(optarg, range - optarg); @@ -820,9 +858,28 @@ static int parse_argv(int argc, char *argv[]) { log_error("Failed to parse UID: %s", optarg); return -EINVAL; } + + arg_userns_mode = USER_NAMESPACE_FIXED; + } + + arg_settings_mask |= SETTING_USERNS; + break; + + case 'U': + if (userns_supported()) { + arg_userns_mode = USER_NAMESPACE_PICK; + arg_uid_shift = UID_INVALID; + arg_uid_range = UINT32_C(0x10000); + + arg_settings_mask |= SETTING_USERNS; } - arg_userns = true; + break; + + case ARG_PRIVATE_USERS_CHOWN: + arg_userns_chown = true; + + arg_settings_mask |= SETTING_USERNS; break; case ARG_KILL_SIGNAL: @@ -893,6 +950,9 @@ static int parse_argv(int argc, char *argv[]) { if (arg_share_system) arg_register = false; + if (arg_userns_mode == USER_NAMESPACE_PICK) + arg_userns_chown = true; + if (arg_start_mode != START_PID1 && arg_share_system) { log_error("--boot and --share-system may not be combined."); return -EINVAL; @@ -933,8 +993,15 @@ static int parse_argv(int argc, char *argv[]) { return -EINVAL; } - if (arg_userns && access("/proc/self/uid_map", F_OK) < 0) - return log_error_errno(EOPNOTSUPP, "--private-users= is not supported, kernel compiled without user namespace support."); + if (arg_userns_mode != USER_NAMESPACE_NO && !userns_supported()) { + log_error("--private-users= is not supported, kernel compiled without user namespace support."); + return -EOPNOTSUPP; + } + + if (arg_userns_chown && arg_read_only) { + log_error("--read-only and --private-users-chown may not be combined."); + return -EINVAL; + } if (argc > optind) { arg_parameters = strv_copy(argv + optind); @@ -993,7 +1060,7 @@ static int verify_arguments(void) { static int userns_lchown(const char *p, uid_t uid, gid_t gid) { assert(p); - if (!arg_userns) + if (arg_userns_mode == USER_NAMESPACE_NO) return 0; if (uid == UID_INVALID && gid == GID_INVALID) @@ -1397,7 +1464,7 @@ static int setup_journal(const char *directory) { if (sd_id128_equal(arg_uuid, this_id)) { log_full(try ? LOG_WARNING : LOG_ERR, - "Host and machine ids are equal (%s): refusing to link journals", id); + "Host and machine ids are equal (%s): refusing to link journals", sd_id128_to_string(arg_uuid, id)); if (try) return 0; return -EEXIST; @@ -2218,6 +2285,29 @@ static int setup_machine_id(const char *directory) { return 0; } +static int recursive_chown(const char *directory, uid_t shift, uid_t range) { + int r; + + assert(directory); + + if (arg_userns_mode == USER_NAMESPACE_NO || !arg_userns_chown) + return 0; + + r = path_patch_uid(directory, arg_uid_shift, arg_uid_range); + if (r == -EOPNOTSUPP) + return log_error_errno(r, "Automatic UID/GID adjusting is only supported for UID/GID ranges starting at multiples of 2^16 with a range of 2^16."); + if (r == -EBADE) + return log_error_errno(r, "Upper 16 bits of root directory UID and GID do not match."); + if (r < 0) + return log_error_errno(r, "Failed to adjust UID/GID shift of OS tree: %m"); + if (r == 0) + log_debug("Root directory of image is already owned by the right UID/GID range, skipping recursive chown operation."); + else + log_debug("Patched directory tree to match UID/GID range."); + + return r; +} + static int mount_devices( const char *where, const char *root_device, bool root_device_rw, @@ -2435,7 +2525,7 @@ static int determine_names(void) { static int determine_uid_shift(const char *directory) { int r; - if (!arg_userns) { + if (arg_userns_mode == USER_NAMESPACE_NO) { arg_uid_shift = 0; return 0; } @@ -2462,7 +2552,6 @@ static int determine_uid_shift(const char *directory) { return -EINVAL; } - log_info("Using user namespaces with base " UID_FMT " and range " UID_FMT ".", arg_uid_shift, arg_uid_range); return 0; } @@ -2499,7 +2588,7 @@ static int inner_child( cg_unified_flush(); - if (arg_userns) { + if (arg_userns_mode != USER_NAMESPACE_NO) { /* Tell the parent, that it now can write the UID map. */ (void) barrier_place(barrier); /* #1 */ @@ -2510,7 +2599,14 @@ static int inner_child( } } - r = mount_all(NULL, arg_userns, true, arg_uid_shift, arg_private_network, arg_uid_range, arg_selinux_apifs_context); + r = mount_all(NULL, + arg_userns_mode != USER_NAMESPACE_NO, + true, + arg_private_network, + arg_uid_shift, + arg_uid_range, + arg_selinux_apifs_context); + if (r < 0) return r; @@ -2749,7 +2845,8 @@ static int outer_child( if (r < 0) return r; - if (arg_userns) { + if (arg_userns_mode != USER_NAMESPACE_NO) { + /* Let the parent know which UID shift we read from the image */ l = send(uid_shift_socket, &arg_uid_shift, sizeof(arg_uid_shift), MSG_NOSIGNAL); if (l < 0) return log_error_errno(errno, "Failed to send UID shift: %m"); @@ -2757,17 +2854,49 @@ static int outer_child( log_error("Short write while sending UID shift."); return -EIO; } + + if (arg_userns_mode == USER_NAMESPACE_PICK) { + /* When we are supposed to pick the UID shift, the parent will check now whether the UID shift + * we just read from the image is available. If yes, it will send the UID shift back to us, if + * not it will pick a different one, and send it back to us. */ + + l = recv(uid_shift_socket, &arg_uid_shift, sizeof(arg_uid_shift), 0); + if (l < 0) + return log_error_errno(errno, "Failed to recv UID shift: %m"); + if (l != sizeof(arg_uid_shift)) { + log_error("Short read while recieving UID shift."); + return -EIO; + } + } + + log_info("Selected user namespace base " UID_FMT " and range " UID_FMT ".", arg_uid_shift, arg_uid_range); } /* Turn directory into bind mount */ if (mount(directory, directory, NULL, MS_BIND|MS_REC, NULL) < 0) return log_error_errno(errno, "Failed to make bind mount: %m"); - r = setup_volatile(directory, arg_volatile_mode, arg_userns, arg_uid_shift, arg_uid_range, arg_selinux_context); + r = recursive_chown(directory, arg_uid_shift, arg_uid_range); if (r < 0) return r; - r = setup_volatile_state(directory, arg_volatile_mode, arg_userns, arg_uid_shift, arg_uid_range, arg_selinux_context); + r = setup_volatile( + directory, + arg_volatile_mode, + arg_userns_mode != USER_NAMESPACE_NO, + arg_uid_shift, + arg_uid_range, + arg_selinux_context); + if (r < 0) + return r; + + r = setup_volatile_state( + directory, + arg_volatile_mode, + arg_userns_mode != USER_NAMESPACE_NO, + arg_uid_shift, + arg_uid_range, + arg_selinux_context); if (r < 0) return r; @@ -2781,7 +2910,13 @@ static int outer_child( return log_error_errno(r, "Failed to make tree read-only: %m"); } - r = mount_all(directory, arg_userns, false, arg_private_network, arg_uid_shift, arg_uid_range, arg_selinux_apifs_context); + r = mount_all(directory, + arg_userns_mode != USER_NAMESPACE_NO, + false, + arg_private_network, + arg_uid_shift, + arg_uid_range, + arg_selinux_apifs_context); if (r < 0) return r; @@ -2823,11 +2958,24 @@ static int outer_child( if (r < 0) return r; - r = mount_custom(directory, arg_custom_mounts, arg_n_custom_mounts, arg_userns, arg_uid_shift, arg_uid_range, arg_selinux_apifs_context); + r = mount_custom( + directory, + arg_custom_mounts, + arg_n_custom_mounts, + arg_userns_mode != USER_NAMESPACE_NO, + arg_uid_shift, + arg_uid_range, + arg_selinux_apifs_context); if (r < 0) return r; - r = mount_cgroups(directory, arg_unified_cgroup_hierarchy, arg_userns, arg_uid_shift, arg_uid_range, arg_selinux_apifs_context); + r = mount_cgroups( + directory, + arg_unified_cgroup_hierarchy, + arg_userns_mode != USER_NAMESPACE_NO, + arg_uid_shift, + arg_uid_range, + arg_selinux_apifs_context); if (r < 0) return r; @@ -2838,7 +2986,7 @@ static int outer_child( pid = raw_clone(SIGCHLD|CLONE_NEWNS| (arg_share_system ? 0 : CLONE_NEWIPC|CLONE_NEWPID|CLONE_NEWUTS) | (arg_private_network ? CLONE_NEWNET : 0) | - (arg_userns ? CLONE_NEWUSER : 0), + (arg_userns_mode != USER_NAMESPACE_NO ? CLONE_NEWUSER : 0), NULL); if (pid < 0) return log_error_errno(errno, "Failed to fork inner child: %m"); @@ -2882,6 +3030,61 @@ static int outer_child( return 0; } +static int uid_shift_pick(uid_t *shift, LockFile *ret_lock_file) { + unsigned n_tries = 100; + uid_t candidate; + int r; + + assert(shift); + assert(ret_lock_file); + assert(arg_userns_mode == USER_NAMESPACE_PICK); + assert(arg_uid_range == 0x10000U); + + candidate = *shift; + + (void) mkdir("/run/systemd/nspawn-uid", 0755); + + for (;;) { + char lock_path[strlen("/run/systemd/nspawn-uid/") + DECIMAL_STR_MAX(uid_t) + 1]; + _cleanup_release_lock_file_ LockFile lf = LOCK_FILE_INIT; + + if (--n_tries <= 0) + return -EBUSY; + + if (candidate < UID_SHIFT_PICK_MIN || candidate > UID_SHIFT_PICK_MAX) + goto next; + if ((candidate & UINT32_C(0xFFFF)) != 0) + goto next; + + xsprintf(lock_path, "/run/systemd/nspawn-uid/" UID_FMT, candidate); + r = make_lock_file(lock_path, LOCK_EX|LOCK_NB, &lf); + if (r == -EBUSY) /* Range already taken by another nspawn instance */ + goto next; + if (r < 0) + return r; + + /* Make some superficial checks whether the range is currently known in the user database */ + if (getpwuid(candidate)) + goto next; + if (getpwuid(candidate + UINT32_C(0xFFFE))) + goto next; + if (getgrgid(candidate)) + goto next; + if (getgrgid(candidate + UINT32_C(0xFFFE))) + goto next; + + *ret_lock_file = lf; + lf = (struct LockFile) LOCK_FILE_INIT; + *shift = candidate; + return 0; + + next: + random_bytes(&candidate, sizeof(candidate)); + candidate = (candidate % (UID_SHIFT_PICK_MAX - UID_SHIFT_PICK_MIN)) + UID_SHIFT_PICK_MIN; + candidate &= (uid_t) UINT32_C(0xFFFF0000); + } +} + static int setup_uid_map(pid_t pid) { char uid_map[strlen("/proc//uid_map") + DECIMAL_STR_MAX(uid_t) + 1], line[DECIMAL_STR_MAX(uid_t)*3+3+1]; int r; @@ -3113,6 +3316,19 @@ static int load_settings(void) { } } + if ((arg_settings_mask & SETTING_USERNS) == 0 && + settings->userns_mode != _USER_NAMESPACE_MODE_INVALID) { + + if (!arg_settings_trusted) + log_warning("Ignoring PrivateUsers= and PrivateUsersChown= settings, file %s is not trusted.", p); + else { + arg_userns_mode = settings->userns_mode; + arg_uid_shift = settings->uid_shift; + arg_uid_range = settings->uid_range; + arg_userns_chown = settings->userns_chown; + } + } + return 0; } @@ -3123,7 +3339,7 @@ int main(int argc, char *argv[]) { _cleanup_close_ int master = -1, image_fd = -1; _cleanup_fdset_free_ FDSet *fds = NULL; int r, n_fd_passed, loop_nr = -1; - char veth_name[IFNAMSIZ]; + char veth_name[IFNAMSIZ] = ""; bool secondary = false, remove_subvol = false; sigset_t mask_chld; pid_t pid = 0; @@ -3351,20 +3567,42 @@ int main(int argc, char *argv[]) { } for (;;) { - _cleanup_close_pair_ int kmsg_socket_pair[2] = { -1, -1 }, rtnl_socket_pair[2] = { -1, -1 }, - pid_socket_pair[2] = { -1, -1 }, uuid_socket_pair[2] = { -1, -1 }, uid_shift_socket_pair[2] = { -1, -1 }; - ContainerStatus container_status; - _cleanup_(barrier_destroy) Barrier barrier = BARRIER_NULL; static const struct sigaction sa = { .sa_handler = nop_signal_handler, .sa_flags = SA_NOCLDSTOP, }; - int ifi = 0; - ssize_t l; + + _cleanup_release_lock_file_ LockFile uid_shift_lock = LOCK_FILE_INIT; + _cleanup_close_ int etc_passwd_lock = -1; + _cleanup_close_pair_ int + kmsg_socket_pair[2] = { -1, -1 }, + rtnl_socket_pair[2] = { -1, -1 }, + pid_socket_pair[2] = { -1, -1 }, + uuid_socket_pair[2] = { -1, -1 }, + uid_shift_socket_pair[2] = { -1, -1 }; + _cleanup_(barrier_destroy) Barrier barrier = BARRIER_NULL; _cleanup_(sd_event_unrefp) sd_event *event = NULL; _cleanup_(pty_forward_freep) PTYForward *forward = NULL; _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + ContainerStatus container_status; char last_char = 0; + int ifi = 0; + ssize_t l; + + if (arg_userns_mode == USER_NAMESPACE_PICK) { + /* When we shall pick the UID/GID range, let's first lock /etc/passwd, so that we can safely + * check with getpwuid() if the specific user already exists. Note that /etc might be + * read-only, in which case this will fail with EROFS. But that's really OK, as in that case we + * can be reasonably sure that no users are going to be added. Note that getpwuid() checks are + * really just an extra safety net. We kinda assume that the UID range we allocate from is + * really ours. */ + + etc_passwd_lock = take_etc_passwd_lock(NULL); + if (etc_passwd_lock < 0 && etc_passwd_lock != -EROFS) { + log_error_errno(r, "Failed to take /etc/passwd lock: %m"); + goto finish; + } + } r = barrier_create(&barrier); if (r < 0) { @@ -3392,7 +3630,7 @@ int main(int argc, char *argv[]) { goto finish; } - if (arg_userns) + if (arg_userns_mode != USER_NAMESPACE_NO) if (socketpair(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0, uid_shift_socket_pair) < 0) { r = log_error_errno(errno, "Failed to create uid shift socket pair: %m"); goto finish; @@ -3468,6 +3706,43 @@ int main(int argc, char *argv[]) { uuid_socket_pair[1] = safe_close(uuid_socket_pair[1]); uid_shift_socket_pair[1] = safe_close(uid_shift_socket_pair[1]); + if (arg_userns_mode != USER_NAMESPACE_NO) { + /* The child just let us know the UID shift it might have read from the image. */ + l = recv(uid_shift_socket_pair[0], &arg_uid_shift, sizeof(arg_uid_shift), 0); + if (l < 0) { + r = log_error_errno(errno, "Failed to read UID shift: %m"); + goto finish; + } + if (l != sizeof(arg_uid_shift)) { + log_error("Short read while reading UID shift."); + r = EIO; + goto finish; + } + + if (arg_userns_mode == USER_NAMESPACE_PICK) { + /* If we are supposed to pick the UID shift, let's try to use the shift read from the + * image, but if that's already in use, pick a new one, and report back to the child, + * which one we now picked. */ + + r = uid_shift_pick(&arg_uid_shift, &uid_shift_lock); + if (r < 0) { + log_error_errno(r, "Failed to pick suitable UID/GID range: %m"); + goto finish; + } + + l = send(uid_shift_socket_pair[0], &arg_uid_shift, sizeof(arg_uid_shift), MSG_NOSIGNAL); + if (l < 0) { + r = log_error_errno(errno, "Failed to send UID shift: %m"); + goto finish; + } + if (l != sizeof(arg_uid_shift)) { + log_error("Short write while writing UID shift."); + r = -EIO; + goto finish; + } + } + } + /* Wait for the outer child. */ r = wait_for_terminate_and_warn("namespace helper", pid, NULL); if (r < 0) @@ -3504,24 +3779,13 @@ int main(int argc, char *argv[]) { log_debug("Init process invoked as PID " PID_FMT, pid); - if (arg_userns) { + if (arg_userns_mode != USER_NAMESPACE_NO) { if (!barrier_place_and_sync(&barrier)) { /* #1 */ log_error("Child died too early."); r = -ESRCH; goto finish; } - l = recv(uid_shift_socket_pair[0], &arg_uid_shift, sizeof(arg_uid_shift), 0); - if (l < 0) { - r = log_error_errno(errno, "Failed to read UID shift: %m"); - goto finish; - } - if (l != sizeof(arg_uid_shift)) { - log_error("Short read while reading UID shift."); - r = EIO; - goto finish; - } - r = setup_uid_map(pid); if (r < 0) goto finish; @@ -3619,6 +3883,10 @@ int main(int argc, char *argv[]) { goto finish; } + /* At this point we have made use of the UID we picked, and thus nss-mymachines will make them appear + * in getpwuid(), thus we can release the /etc/passwd lock. */ + etc_passwd_lock = safe_close(etc_passwd_lock); + sd_notifyf(false, "READY=1\n" "STATUS=Container running.\n" diff --git a/src/nspawn/test-patch-uid.c b/src/nspawn/test-patch-uid.c new file mode 100644 index 0000000000..11c5321788 --- /dev/null +++ b/src/nspawn/test-patch-uid.c @@ -0,0 +1,61 @@ +/*** + This file is part of systemd. + + Copyright 2016 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <stdlib.h> + +#include "log.h" +#include "nspawn-patch-uid.h" +#include "user-util.h" +#include "util.h" + +int main(int argc, char *argv[]) { + uid_t shift, range; + int r; + + log_set_max_level(LOG_DEBUG); + log_parse_environment(); + log_open(); + + if (argc != 4) { + log_error("Expected PATH SHIFT RANGE parameters."); + return EXIT_FAILURE; + } + + r = parse_uid(argv[2], &shift); + if (r < 0) { + log_error_errno(r, "Failed to parse UID shift %s.", argv[2]); + return EXIT_FAILURE; + } + + r = parse_gid(argv[3], &range); + if (r < 0) { + log_error_errno(r, "Failed to parse UID range %s.", argv[3]); + return EXIT_FAILURE; + } + + r = path_patch_uid(argv[1], shift, range); + if (r < 0) { + log_error_errno(r, "Failed to patch directory tree: %m"); + return EXIT_FAILURE; + } + + log_info("Changed: %s", yes_no(r)); + + return EXIT_SUCCESS; +} diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c index da479aec8d..2b755cea28 100644 --- a/src/shared/bus-unit-util.c +++ b/src/shared/bus-unit-util.c @@ -684,7 +684,7 @@ static int bus_job_get_service_result(BusWaitForJobs *d, char **result) { static const struct { const char *result, *explanation; } explanations [] = { - { "resources", "a configured resource limit was exceeded" }, + { "resources", "of unavailable resources or another system error" }, { "timeout", "a timeout was exceeded" }, { "exit-code", "the control process exited with error code" }, { "signal", "a fatal signal was delivered to the control process" }, diff --git a/src/shared/dropin.c b/src/shared/dropin.c index cc1acd6f23..b9cd952ac8 100644 --- a/src/shared/dropin.c +++ b/src/shared/dropin.c @@ -160,7 +160,7 @@ static int iterate_dir( if (!de) break; - if (hidden_file(de->d_name)) + if (hidden_or_backup_file(de->d_name)) continue; f = strjoin(path, "/", de->d_name, NULL); diff --git a/src/shared/install.c b/src/shared/install.c index b74ff6de22..f02d81504f 100644 --- a/src/shared/install.c +++ b/src/shared/install.c @@ -68,6 +68,25 @@ typedef struct { static int unit_file_lookup_state(UnitFileScope scope, const LookupPaths *paths, const char *name, UnitFileState *ret); +bool unit_type_may_alias(UnitType type) { + return IN_SET(type, + UNIT_SERVICE, + UNIT_SOCKET, + UNIT_TARGET, + UNIT_DEVICE, + UNIT_TIMER, + UNIT_PATH); +} + +bool unit_type_may_template(UnitType type) { + return IN_SET(type, + UNIT_SERVICE, + UNIT_SOCKET, + UNIT_TARGET, + UNIT_TIMER, + UNIT_PATH); +} + static int in_search_path(const LookupPaths *p, const char *path) { _cleanup_free_ char *parent = NULL; char **i; @@ -898,6 +917,36 @@ fail: return r; } +static int config_parse_alias( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + const char *name; + UnitType type; + + assert(filename); + assert(lvalue); + assert(rvalue); + + name = basename(filename); + type = unit_name_to_type(name); + if (!unit_type_may_alias(type)) + return log_syntax(unit, LOG_WARNING, filename, line, 0, + "Aliases are not allowed for %s units, ignoring.", + unit_type_to_string(type)); + + return config_parse_strv(unit, filename, line, section, section_line, + lvalue, ltype, rvalue, data, userdata); +} + static int config_parse_also( const char *unit, const char *filename, @@ -954,6 +1003,7 @@ static int config_parse_default_instance( void *userdata) { UnitFileInstallInfo *i = data; + const char *name; char *printed; int r; @@ -961,6 +1011,15 @@ static int config_parse_default_instance( assert(lvalue); assert(rvalue); + name = basename(filename); + if (unit_name_is_valid(name, UNIT_NAME_INSTANCE)) + /* When enabling an instance, we might be using a template unit file, + * but we should ignore DefaultInstance silently. */ + return 0; + if (!unit_name_is_valid(name, UNIT_NAME_TEMPLATE)) + return log_syntax(unit, LOG_WARNING, filename, line, 0, + "DefaultInstance only makes sense for template units, ignoring."); + r = install_full_printf(i, rvalue, &printed); if (r < 0) return r; @@ -983,7 +1042,7 @@ static int unit_file_load( SearchFlags flags) { const ConfigTableItem items[] = { - { "Install", "Alias", config_parse_strv, 0, &info->aliases }, + { "Install", "Alias", config_parse_alias, 0, &info->aliases }, { "Install", "WantedBy", config_parse_strv, 0, &info->wanted_by }, { "Install", "RequiredBy", config_parse_strv, 0, &info->required_by }, { "Install", "DefaultInstance", config_parse_default_instance, 0, info }, @@ -991,6 +1050,8 @@ static int unit_file_load( {} }; + const char *name; + UnitType type; _cleanup_fclose_ FILE *f = NULL; _cleanup_close_ int fd = -1; struct stat st; @@ -1000,6 +1061,12 @@ static int unit_file_load( assert(info); assert(path); + name = basename(path); + type = unit_name_to_type(name); + if (unit_name_is_valid(name, UNIT_NAME_TEMPLATE|UNIT_NAME_INSTANCE) && + !unit_type_may_template(type)) + return log_error_errno(EINVAL, "Unit type %s cannot be templated.", unit_type_to_string(type)); + if (!(flags & SEARCH_LOAD)) { r = lstat(path, &st); if (r < 0) @@ -2653,7 +2720,9 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(UnitFileList*, unit_file_list_free_one); int unit_file_get_list( UnitFileScope scope, const char *root_dir, - Hashmap *h) { + Hashmap *h, + char **states, + char **patterns) { _cleanup_lookup_paths_free_ LookupPaths paths = {}; char **i; @@ -2685,6 +2754,9 @@ int unit_file_get_list( if (!unit_name_is_valid(de->d_name, UNIT_NAME_ANY)) continue; + if (!strv_fnmatch_or_empty(patterns, de->d_name, FNM_NOESCAPE)) + continue; + if (hashmap_get(h, de->d_name)) continue; @@ -2705,6 +2777,10 @@ int unit_file_get_list( if (r < 0) f->state = UNIT_FILE_BAD; + if (!strv_isempty(states) && + !strv_contains(states, unit_file_state_to_string(f->state))) + continue; + r = hashmap_put(h, basename(f->path), f); if (r < 0) return r; diff --git a/src/shared/install.h b/src/shared/install.h index 4133faffa2..5812447c5b 100644 --- a/src/shared/install.h +++ b/src/shared/install.h @@ -138,6 +138,9 @@ static inline bool UNIT_FILE_INSTALL_INFO_HAS_ALSO(UnitFileInstallInfo *i) { return !strv_isempty(i->also); } +bool unit_type_may_alias(UnitType type) _const_; +bool unit_type_may_template(UnitType type) _const_; + int unit_file_enable( UnitFileScope scope, bool runtime, @@ -232,7 +235,7 @@ int unit_file_add_dependency( int unit_file_get_state(UnitFileScope scope, const char *root_dir, const char *filename, UnitFileState *ret); int unit_file_exists(UnitFileScope scope, const LookupPaths *paths, const char *name); -int unit_file_get_list(UnitFileScope scope, const char *root_dir, Hashmap *h); +int unit_file_get_list(UnitFileScope scope, const char *root_dir, Hashmap *h, char **states, char **patterns); Hashmap* unit_file_list_free(Hashmap *h); int unit_file_changes_add(UnitFileChange **changes, unsigned *n_changes, UnitFileChangeType type, const char *path, const char *source); diff --git a/src/shared/logs-show.c b/src/shared/logs-show.c index cd3d53669c..9351b85eed 100644 --- a/src/shared/logs-show.c +++ b/src/shared/logs-show.c @@ -287,7 +287,10 @@ static int output_short( if (r < 0) return r; } - + if (r == -EBADMSG) { + log_debug_errno(r, "Skipping message we can't read: %m"); + return 0; + } if (r < 0) return log_error_errno(r, "Failed to get journal fields: %m"); diff --git a/src/shared/machine-image.c b/src/shared/machine-image.c index bebfc40efe..66f58ecd92 100644 --- a/src/shared/machine-image.c +++ b/src/shared/machine-image.c @@ -423,7 +423,7 @@ int image_remove(Image *i) { case IMAGE_DIRECTORY: /* Allow deletion of read-only directories */ - (void) chattr_path(i->path, false, FS_IMMUTABLE_FL); + (void) chattr_path(i->path, 0, FS_IMMUTABLE_FL); r = rm_rf(i->path, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME); if (r < 0) return r; @@ -505,7 +505,7 @@ int image_rename(Image *i, const char *new_name) { (void) read_attr_path(i->path, &file_attr); if (file_attr & FS_IMMUTABLE_FL) - (void) chattr_path(i->path, false, FS_IMMUTABLE_FL); + (void) chattr_path(i->path, 0, FS_IMMUTABLE_FL); /* fall through */ @@ -538,7 +538,7 @@ int image_rename(Image *i, const char *new_name) { /* Restore the immutable bit, if it was set before */ if (file_attr & FS_IMMUTABLE_FL) - (void) chattr_path(new_path, true, FS_IMMUTABLE_FL); + (void) chattr_path(new_path, FS_IMMUTABLE_FL, FS_IMMUTABLE_FL); free(i->path); i->path = new_path; @@ -603,13 +603,21 @@ int image_clone(Image *i, const char *new_name, bool read_only) { case IMAGE_SUBVOLUME: case IMAGE_DIRECTORY: + /* If we can we'll always try to create a new btrfs subvolume here, even if the source is a plain + * directory.*/ + new_path = strjoina("/var/lib/machines/", new_name); r = btrfs_subvol_snapshot(i->path, new_path, (read_only ? BTRFS_SNAPSHOT_READ_ONLY : 0) | BTRFS_SNAPSHOT_FALLBACK_COPY | BTRFS_SNAPSHOT_RECURSIVE | BTRFS_SNAPSHOT_QUOTA); + if (r == -EOPNOTSUPP) { + /* No btrfs snapshots supported, create a normal directory then. */ - /* Enable "subtree" quotas for the copy, if we didn't - * copy any quota from the source. */ - (void) btrfs_subvol_auto_qgroup(i->path, 0, true); + r = copy_directory(i->path, new_path, false); + if (r >= 0) + (void) chattr_path(new_path, read_only ? FS_IMMUTABLE_FL : 0, FS_IMMUTABLE_FL); + } else if (r >= 0) + /* Enable "subtree" quotas for the copy, if we didn't copy any quota from the source. */ + (void) btrfs_subvol_auto_qgroup(new_path, 0, true); break; @@ -670,7 +678,7 @@ int image_read_only(Image *i, bool b) { a read-only subvolume, but at least something, and we can read the value back.*/ - r = chattr_path(i->path, b, FS_IMMUTABLE_FL); + r = chattr_path(i->path, b ? FS_IMMUTABLE_FL : 0, FS_IMMUTABLE_FL); if (r < 0) return r; diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index 595d6853c6..9af25e22a4 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -541,6 +541,7 @@ static int get_unit_list( size_t size = c; int r; UnitInfo u; + bool fallback = false; assert(bus); assert(unit_infos); @@ -552,8 +553,7 @@ static int get_unit_list( "org.freedesktop.systemd1", "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", - "ListUnitsFiltered"); - + "ListUnitsByPatterns"); if (r < 0) return bus_log_create_error(r); @@ -561,7 +561,34 @@ static int get_unit_list( if (r < 0) return bus_log_create_error(r); + r = sd_bus_message_append_strv(m, patterns); + if (r < 0) + return bus_log_create_error(r); + r = sd_bus_call(bus, m, 0, &error, &reply); + if (r < 0 && sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_METHOD)) { + /* Fallback to legacy ListUnitsFiltered method */ + fallback = true; + log_debug_errno(r, "Failed to list units: %s Falling back to ListUnitsFiltered method.", bus_error_message(&error, r)); + m = sd_bus_message_unref(m); + sd_bus_error_free(&error); + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "ListUnitsFiltered"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append_strv(m, arg_states); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_call(bus, m, 0, &error, &reply); + } if (r < 0) return log_error_errno(r, "Failed to list units: %s", bus_error_message(&error, r)); @@ -572,7 +599,7 @@ static int get_unit_list( while ((r = bus_parse_unit_info(reply, &u)) > 0) { u.machine = machine; - if (!output_show_unit(&u, patterns)) + if (!output_show_unit(&u, fallback ? patterns : NULL)) continue; if (!GREEDY_REALLOC(*unit_infos, size, c+1)) @@ -1282,7 +1309,7 @@ static int compare_unit_file_list(const void *a, const void *b) { return strcasecmp(basename(u->path), basename(v->path)); } -static bool output_show_unit_file(const UnitFileList *u, char **patterns) { +static bool output_show_unit_file(const UnitFileList *u, char **states, char **patterns) { assert(u); if (!strv_fnmatch_or_empty(patterns, basename(u->path), FNM_NOESCAPE)) @@ -1299,8 +1326,8 @@ static bool output_show_unit_file(const UnitFileList *u, char **patterns) { return false; } - if (!strv_isempty(arg_states) && - !strv_find(arg_states, unit_file_state_to_string(u->state))) + if (!strv_isempty(states) && + !strv_find(states, unit_file_state_to_string(u->state))) return false; return true; @@ -1373,6 +1400,7 @@ static int list_unit_files(int argc, char *argv[], void *userdata) { const char *state; char *path; int r; + bool fallback = false; pager_open(arg_no_pager, false); @@ -1386,7 +1414,7 @@ static int list_unit_files(int argc, char *argv[], void *userdata) { if (!h) return log_oom(); - r = unit_file_get_list(arg_scope, arg_root, h); + r = unit_file_get_list(arg_scope, arg_root, h, arg_states, strv_skip(argv, 1)); if (r < 0) { unit_file_list_free(h); return log_error_errno(r, "Failed to get unit file list: %m"); @@ -1401,7 +1429,7 @@ static int list_unit_files(int argc, char *argv[], void *userdata) { } HASHMAP_FOREACH(u, h, i) { - if (!output_show_unit_file(u, strv_skip(argv, 1))) + if (!output_show_unit_file(u, NULL, NULL)) continue; units[c++] = *u; @@ -1411,6 +1439,7 @@ static int list_unit_files(int argc, char *argv[], void *userdata) { assert(c <= n_units); hashmap_free(h); } else { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus; @@ -1418,15 +1447,44 @@ static int list_unit_files(int argc, char *argv[], void *userdata) { if (r < 0) return r; - r = sd_bus_call_method( + r = sd_bus_message_new_method_call( bus, + &m, "org.freedesktop.systemd1", "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", - "ListUnitFiles", - &error, - &reply, - NULL); + "ListUnitFilesByPatterns"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append_strv(m, arg_states); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append_strv(m, strv_skip(argv, 1)); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_call(bus, m, 0, &error, &reply); + if (r < 0 && sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_METHOD)) { + /* Fallback to legacy ListUnitFiles method */ + fallback = true; + log_debug_errno(r, "Failed to list unit files: %s Falling back to ListUnitsFiles method.", bus_error_message(&error, r)); + m = sd_bus_message_unref(m); + sd_bus_error_free(&error); + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "ListUnitFiles"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_call(bus, m, 0, &error, &reply); + } if (r < 0) return log_error_errno(r, "Failed to list unit files: %s", bus_error_message(&error, r)); @@ -1444,7 +1502,9 @@ static int list_unit_files(int argc, char *argv[], void *userdata) { unit_file_state_from_string(state) }; - if (output_show_unit_file(&units[c], strv_skip(argv, 1))) + if (output_show_unit_file(&units[c], + fallback ? arg_states : NULL, + fallback ? strv_skip(argv, 1) : NULL)) c++; } diff --git a/src/systemd/sd-journal.h b/src/systemd/sd-journal.h index d4c6f409cd..9c36b27157 100644 --- a/src/systemd/sd-journal.h +++ b/src/systemd/sd-journal.h @@ -67,10 +67,11 @@ typedef struct sd_journal sd_journal; /* Open flags */ enum { - SD_JOURNAL_LOCAL_ONLY = 1, - SD_JOURNAL_RUNTIME_ONLY = 2, - SD_JOURNAL_SYSTEM = 4, - SD_JOURNAL_CURRENT_USER = 8, + SD_JOURNAL_LOCAL_ONLY = 1 << 0, + SD_JOURNAL_RUNTIME_ONLY = 1 << 1, + SD_JOURNAL_SYSTEM = 1 << 2, + SD_JOURNAL_CURRENT_USER = 1 << 3, + SD_JOURNAL_OS_ROOT = 1 << 4, SD_JOURNAL_SYSTEM_ONLY = SD_JOURNAL_SYSTEM /* deprecated name */ }; @@ -84,8 +85,10 @@ enum { int sd_journal_open(sd_journal **ret, int flags); int sd_journal_open_directory(sd_journal **ret, const char *path, int flags); +int sd_journal_open_directory_fd(sd_journal **ret, int fd, int flags); int sd_journal_open_files(sd_journal **ret, const char **paths, int flags); -int sd_journal_open_container(sd_journal **ret, const char *machine, int flags); +int sd_journal_open_files_fd(sd_journal **ret, int fds[], unsigned n_fds, int flags); +int sd_journal_open_container(sd_journal **ret, const char *machine, int flags); /* deprecated */ void sd_journal_close(sd_journal *j); int sd_journal_previous(sd_journal *j); diff --git a/src/test/test-copy.c b/src/test/test-copy.c index cb437754b4..68154fc4e8 100644 --- a/src/test/test-copy.c +++ b/src/test/test-copy.c @@ -95,6 +95,8 @@ static void test_copy_tree(void) { char **links = STRV_MAKE("link", "file", "link2", "dir1/file"); char **p, **link; + const char *unixsockp; + struct stat st; log_info("%s", __func__); @@ -102,26 +104,34 @@ static void test_copy_tree(void) { (void) rm_rf(original_dir, REMOVE_ROOT|REMOVE_PHYSICAL); STRV_FOREACH(p, files) { - char *f = strjoina(original_dir, *p); + _cleanup_free_ char *f; + + assert_se(f = strappend(original_dir, *p)); assert_se(mkdir_parents(f, 0755) >= 0); assert_se(write_string_file(f, "file", WRITE_STRING_FILE_CREATE) == 0); } STRV_FOREACH_PAIR(link, p, links) { - char *f = strjoina(original_dir, *p); - char *l = strjoina(original_dir, *link); + _cleanup_free_ char *f, *l; + + assert_se(f = strappend(original_dir, *p)); + assert_se(l = strappend(original_dir, *link)); assert_se(mkdir_parents(l, 0755) >= 0); assert_se(symlink(f, l) == 0); } + unixsockp = strjoina(original_dir, "unixsock"); + assert_se(mknod(unixsockp, S_IFSOCK|0644, 0) >= 0); + assert_se(copy_tree(original_dir, copy_dir, true) == 0); STRV_FOREACH(p, files) { - _cleanup_free_ char *buf = NULL; + _cleanup_free_ char *buf = NULL, *f; size_t sz = 0; - char *f = strjoina(copy_dir, *p); + + assert_se(f = strappend(copy_dir, *p)); assert_se(access(f, F_OK) == 0); assert_se(read_full_file(f, &buf, &sz) == 0); @@ -129,14 +139,19 @@ static void test_copy_tree(void) { } STRV_FOREACH_PAIR(link, p, links) { - _cleanup_free_ char *target = NULL; - char *f = strjoina(original_dir, *p); - char *l = strjoina(copy_dir, *link); + _cleanup_free_ char *target = NULL, *f, *l; + + assert_se(f = strjoin(original_dir, *p, NULL)); + assert_se(l = strjoin(copy_dir, *link, NULL)); assert_se(readlink_and_canonicalize(l, &target) == 0); assert_se(path_equal(f, target)); } + unixsockp = strjoina(copy_dir, "unixsock"); + assert_se(stat(unixsockp, &st) >= 0); + assert_se(S_ISSOCK(st.st_mode)); + assert_se(copy_tree(original_dir, copy_dir, false) < 0); assert_se(copy_tree("/tmp/inexistent/foo/bar/fsdoi", copy_dir, false) < 0); diff --git a/src/test/test-install-root.c b/src/test/test-install-root.c index 2d73c9743b..4680b0336d 100644 --- a/src/test/test-install-root.c +++ b/src/test/test-install-root.c @@ -606,7 +606,7 @@ static void test_preset_and_list(const char *root) { assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "preset-no.service", &state) >= 0 && state == UNIT_FILE_DISABLED); assert_se(h = hashmap_new(&string_hash_ops)); - assert_se(unit_file_get_list(UNIT_FILE_SYSTEM, root, h) >= 0); + assert_se(unit_file_get_list(UNIT_FILE_SYSTEM, root, h, NULL, NULL) >= 0); p = strjoina(root, "/usr/lib/systemd/system/preset-yes.service"); q = strjoina(root, "/usr/lib/systemd/system/preset-no.service"); diff --git a/src/test/test-install.c b/src/test/test-install.c index 50315c1d9a..0ac85f040a 100644 --- a/src/test/test-install.c +++ b/src/test/test-install.c @@ -50,7 +50,7 @@ int main(int argc, char* argv[]) { log_parse_environment(); h = hashmap_new(&string_hash_ops); - r = unit_file_get_list(UNIT_FILE_SYSTEM, NULL, h); + r = unit_file_get_list(UNIT_FILE_SYSTEM, NULL, h, NULL, NULL); assert_se(r == 0); HASHMAP_FOREACH(p, h, i) { diff --git a/src/test/test-path-util.c b/src/test/test-path-util.c index 5d77e2959c..b53324b5e6 100644 --- a/src/test/test-path-util.c +++ b/src/test/test-path-util.c @@ -489,6 +489,27 @@ static void test_filename_is_valid(void) { assert_se(filename_is_valid("o.o")); } +static void test_hidden_or_backup_file(void) { + assert_se(hidden_or_backup_file(".hidden")); + assert_se(hidden_or_backup_file("..hidden")); + assert_se(!hidden_or_backup_file("hidden.")); + + assert_se(hidden_or_backup_file("backup~")); + assert_se(hidden_or_backup_file(".backup~")); + + assert_se(hidden_or_backup_file("lost+found")); + assert_se(hidden_or_backup_file("aquota.user")); + assert_se(hidden_or_backup_file("aquota.group")); + + assert_se(hidden_or_backup_file("test.rpmnew")); + assert_se(hidden_or_backup_file("test.dpkg-old")); + assert_se(hidden_or_backup_file("test.dpkg-remove")); + assert_se(hidden_or_backup_file("test.swp")); + + assert_se(!hidden_or_backup_file("test.rpmnew.")); + assert_se(!hidden_or_backup_file("test.dpkg-old.foo")); +} + int main(int argc, char **argv) { test_path(); test_find_binary(argv[0]); @@ -502,6 +523,7 @@ int main(int argc, char **argv) { test_path_is_mount_point(); test_file_in_same_dir(); test_filename_is_valid(); + test_hidden_or_backup_file(); return 0; } diff --git a/src/test/test-rlimit-util.c b/src/test/test-rlimit-util.c index d9ac9368cd..62afd2de5e 100644 --- a/src/test/test-rlimit-util.c +++ b/src/test/test-rlimit-util.c @@ -99,6 +99,18 @@ int main(int argc, char *argv[]) { test_rlimit_parse_format(RLIMIT_NOFILE, "", 0, 0, -EINVAL, NULL); test_rlimit_parse_format(RLIMIT_NOFILE, "5:4", 0, 0, -EILSEQ, NULL); test_rlimit_parse_format(RLIMIT_NOFILE, "5:4:3", 0, 0, -EINVAL, NULL); + test_rlimit_parse_format(RLIMIT_NICE, "20", 20, 20, 0, "20"); + test_rlimit_parse_format(RLIMIT_NICE, "40", 40, 40, 0, "40"); + test_rlimit_parse_format(RLIMIT_NICE, "41", 41, 41, -ERANGE, "41"); + test_rlimit_parse_format(RLIMIT_NICE, "0", 0, 0, 0, "0"); + test_rlimit_parse_format(RLIMIT_NICE, "-7", 27, 27, 0, "27"); + test_rlimit_parse_format(RLIMIT_NICE, "-20", 40, 40, 0, "40"); + test_rlimit_parse_format(RLIMIT_NICE, "-21", 41, 41, -ERANGE, "41"); + test_rlimit_parse_format(RLIMIT_NICE, "-0", 20, 20, 0, "20"); + test_rlimit_parse_format(RLIMIT_NICE, "+7", 13, 13, 0, "13"); + test_rlimit_parse_format(RLIMIT_NICE, "+19", 1, 1, 0, "1"); + test_rlimit_parse_format(RLIMIT_NICE, "+20", 0, 0, -ERANGE, "0"); + test_rlimit_parse_format(RLIMIT_NICE, "+0", 20, 20, 0, "20"); return 0; } diff --git a/src/test/test-unit-file.c b/src/test/test-unit-file.c index 114ddf8478..c340673c6c 100644 --- a/src/test/test-unit-file.c +++ b/src/test/test-unit-file.c @@ -53,7 +53,7 @@ static int test_unit_file_get_set(void) { h = hashmap_new(&string_hash_ops); assert_se(h); - r = unit_file_get_list(UNIT_FILE_SYSTEM, NULL, h); + r = unit_file_get_list(UNIT_FILE_SYSTEM, NULL, h, NULL, NULL); if (r == -EPERM || r == -EACCES) { printf("Skipping test: unit_file_get_list: %s", strerror(-r)); diff --git a/src/tty-ask-password-agent/tty-ask-password-agent.c b/src/tty-ask-password-agent/tty-ask-password-agent.c index 7b67831e54..c7ded451a2 100644 --- a/src/tty-ask-password-agent/tty-ask-password-agent.c +++ b/src/tty-ask-password-agent/tty-ask-password-agent.c @@ -481,7 +481,7 @@ static int show_passwords(void) { if (de->d_type != DT_REG) continue; - if (hidden_file(de->d_name)) + if (hidden_or_backup_file(de->d_name)) continue; if (!startswith(de->d_name, "ask.")) |