X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=blobdiff_plain;f=src%2Ftmpfiles%2Ftmpfiles.c;h=5d0f571bea605f7a15838e2dbf643309911418b0;hp=5db827eca204e4b9fbdff86e9ca35f0c23bddb39;hb=54693d9bfa855841e8097d7a6b8c8d7acc068004;hpb=e9a5ef7cddcfcdb29b5aef3896931132b6fd5165 diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c index 5db827eca..5d0f571be 100644 --- a/src/tmpfiles/tmpfiles.c +++ b/src/tmpfiles/tmpfiles.c @@ -38,13 +38,18 @@ #include #include #include +#include #include "log.h" #include "util.h" +#include "macro.h" #include "mkdir.h" +#include "path-util.h" #include "strv.h" #include "label.h" #include "set.h" +#include "conf-files.h" +#include "capability.h" /* This reads all files listed in /etc/tmpfiles.d/?*.conf and creates * them in the file system. This is intended to be used to create @@ -87,6 +92,8 @@ typedef struct Item { bool gid_set:1; bool mode_set:1; bool age_set:1; + + bool keep_first_level:1; } Item; static Hashmap *items = NULL, *globs = NULL; @@ -98,6 +105,17 @@ static bool arg_remove = false; static const char *arg_prefix = NULL; +static const char * const conf_file_dirs[] = { + "/etc/tmpfiles.d", + "/run/tmpfiles.d", + "/usr/local/lib/tmpfiles.d", + "/usr/lib/tmpfiles.d", +#ifdef HAVE_SPLIT_USR + "/lib/tmpfiles.d", +#endif + NULL +}; + #define MAX_DEPTH 256 static bool needs_glob(ItemType t) { @@ -206,7 +224,8 @@ static int dir_cleanup( usec_t cutoff, dev_t rootdev, bool mountpoint, - int maxdepth) + int maxdepth, + bool keep_this_level) { struct dirent *dent; struct timespec times[2]; @@ -244,8 +263,7 @@ static int dir_cleanup( sub_path = NULL; if (asprintf(&sub_path, "%s/%s", p, dent->d_name) < 0) { - log_error("Out of memory"); - r = -ENOMEM; + r = log_oom(); goto finish; } @@ -279,13 +297,23 @@ static int dir_cleanup( continue; } - q = dir_cleanup(sub_path, sub_dir, &s, cutoff, rootdev, false, maxdepth-1); + q = dir_cleanup(sub_path, sub_dir, &s, cutoff, rootdev, false, maxdepth-1, false); closedir(sub_dir); if (q < 0) r = q; } + /* Note: if you are wondering why we don't + * support the sticky bit for excluding + * directories from cleaning like we do it for + * other file system objects: well, the sticky + * bit already has a meaning for directories, + * so we don't want to overload that. */ + + if (keep_this_level) + continue; + /* Ignore ctime, we change it when deleting */ age = MAX(timespec_load(&s.st_mtim), timespec_load(&s.st_atim)); @@ -327,6 +355,11 @@ static int dir_cleanup( if (S_ISCHR(s.st_mode) || S_ISBLK(s.st_mode)) continue; + /* Keep files on this level around if this is + * requested */ + if (keep_this_level) + continue; + age = MAX3(timespec_load(&s.st_mtim), timespec_load(&s.st_atim), timespec_load(&s.st_ctim)); @@ -415,7 +448,7 @@ static int clean_item(Item *i) { mountpoint = s.st_dev != ps.st_dev || (s.st_dev == ps.st_dev && s.st_ino == ps.st_ino); - r = dir_cleanup(i->path, d, &s, cutoff, s.st_dev, mountpoint, MAX_DEPTH); + r = dir_cleanup(i->path, d, &s, cutoff, s.st_dev, mountpoint, MAX_DEPTH, i->keep_first_level); finish: if (d) @@ -441,7 +474,69 @@ static int item_set_perms(Item *i, const char *path) { return -errno; } - return label_fix(path, false); + return label_fix(path, false, false); +} + +static int write_one_file(Item *i, const char *path) { + int r, e, fd, flags; + struct stat st; + mode_t u; + + flags = i->type == CREATE_FILE ? O_CREAT|O_APPEND : + i->type == TRUNCATE_FILE ? O_CREAT|O_TRUNC : 0; + + u = umask(0); + label_context_set(path, S_IFREG); + fd = open(path, flags|O_NDELAY|O_CLOEXEC|O_WRONLY|O_NOCTTY|O_NOFOLLOW, i->mode); + e = errno; + label_context_clear(); + umask(u); + errno = e; + + if (fd < 0) { + if (i->type == WRITE_FILE && errno == ENOENT) + return 0; + + log_error("Failed to create file %s: %m", path); + return -errno; + } + + if (i->argument) { + ssize_t n; + size_t l; + _cleanup_free_ char *unescaped; + + unescaped = cunescape(i->argument); + if (unescaped == NULL) + return log_oom(); + + l = strlen(unescaped); + n = write(fd, unescaped, l); + + if (n < 0 || (size_t) n < l) { + log_error("Failed to write file %s: %s", path, n < 0 ? strerror(-n) : "Short write"); + close_nointr_nofail(fd); + return n < 0 ? n : -EIO; + } + } + + close_nointr_nofail(fd); + + if (stat(path, &st) < 0) { + log_error("stat(%s) failed: %m", path); + return -errno; + } + + if (!S_ISREG(st.st_mode)) { + log_error("%s is not a file.", path); + return -EEXIST; + } + + r = item_set_perms(i, path); + if (r < 0) + return r; + + return 0; } static int recursive_relabel_children(Item *i, const char *path) { @@ -577,75 +672,18 @@ static int create_item(Item *i) { case CREATE_FILE: case TRUNCATE_FILE: - case WRITE_FILE: { - int fd, flags; - - flags = i->type == CREATE_FILE ? O_CREAT|O_APPEND : - i->type == TRUNCATE_FILE ? O_CREAT|O_TRUNC : 0; - - u = umask(0); - label_context_set(i->path, S_IFREG); - fd = open(i->path, flags|O_NDELAY|O_CLOEXEC|O_WRONLY|O_NOCTTY|O_NOFOLLOW, i->mode); - e = errno; - label_context_clear(); - umask(u); - errno = e; - - if (fd < 0) { - if (i->type == WRITE_FILE && errno == ENOENT) - break; - - log_error("Failed to create file %s: %m", i->path); - return -errno; - } - - if (i->argument) { - ssize_t n; - size_t l; - struct iovec iovec[2]; - static const char new_line = '\n'; - - l = strlen(i->argument); - - zero(iovec); - iovec[0].iov_base = i->argument; - iovec[0].iov_len = l; - - iovec[1].iov_base = (void*) &new_line; - iovec[1].iov_len = 1; - - n = writev(fd, iovec, 2); - if (n < 0 || (size_t) n != l+1) { - log_error("Failed to write file %s: %s", i->path, n < 0 ? strerror(-n) : "Short"); - close_nointr_nofail(fd); - return n < 0 ? n : -EIO; - } - } - - close_nointr_nofail(fd); - - if (stat(i->path, &st) < 0) { - log_error("stat(%s) failed: %m", i->path); - return -errno; - } - - if (!S_ISREG(st.st_mode)) { - log_error("%s is not a file.", i->path); - return -EEXIST; - } - - r = item_set_perms(i, i->path); + case WRITE_FILE: + r = glob_item(i, write_one_file); if (r < 0) return r; break; - } case TRUNCATE_DIRECTORY: case CREATE_DIRECTORY: u = umask(0); - mkdir_parents(i->path, 0755); + mkdir_parents_label(i->path, 0755); r = mkdir(i->path, i->mode); umask(u); @@ -729,10 +767,23 @@ static int create_item(Item *i) { case CREATE_BLOCK_DEVICE: case CREATE_CHAR_DEVICE: { + mode_t file_type; + + if (have_effective_cap(CAP_MKNOD) == 0) { + /* In a container we lack CAP_MKNOD. We + shouldnt attempt to create the device node in + that case to avoid noise, and we don't support + virtualized devices in containers anyway. */ + + log_debug("We lack CAP_MKNOD, skipping creation of device node %s.", i->path); + return 0; + } + + file_type = (i->type == CREATE_BLOCK_DEVICE ? S_IFBLK : S_IFCHR); u = umask(0); - label_context_set(i->path, CREATE_BLOCK_DEVICE ? S_IFBLK : S_IFCHR); - r = mknod(i->path, i->mode | (i->type == CREATE_BLOCK_DEVICE ? S_IFBLK : S_IFCHR), i->major_minor); + label_context_set(i->path, file_type); + r = mknod(i->path, i->mode | file_type, i->major_minor); e = errno; label_context_clear(); umask(u); @@ -748,7 +799,7 @@ static int create_item(Item *i) { return -errno; } - if (i->type == CREATE_BLOCK_DEVICE ? !S_ISBLK(st.st_mode) : !S_ISCHR(st.st_mode)) { + if ((st.st_mode & S_IFMT) != file_type) { log_error("%s is not a device node.", i->path); return -EEXIST; } @@ -809,7 +860,9 @@ static int remove_item_instance(Item *i, const char *instance) { case TRUNCATE_DIRECTORY: case RECURSIVE_REMOVE_PATH: - r = rm_rf(instance, false, i->type == RECURSIVE_REMOVE_PATH, false); + /* FIXME: we probably should use dir_cleanup() here + * instead of rm_rf() so that 'x' is honoured. */ + r = rm_rf_dangerous(instance, false, i->type == RECURSIVE_REMOVE_PATH, false); if (r < 0 && r != -ENOENT) { log_error("rm_rf(%s): %s", instance, strerror(-r)); return r; @@ -930,10 +983,8 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) { assert(buffer); i = new0(Item, 1); - if (!i) { - log_error("Out of memory"); - return -ENOMEM; - } + if (!i) + return log_oom(); if (sscanf(buffer, "%c " @@ -959,10 +1010,8 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) { n += strspn(buffer+n, WHITESPACE); if (buffer[n] != 0 && (buffer[n] != '-' || buffer[n+1] != 0)) { i->argument = unquote(buffer+n, "\""); - if (!i->argument) { - log_error("Out of memory"); - return -ENOMEM; - } + if (!i->argument) + return log_oom(); } } @@ -1040,7 +1089,7 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) { if (user && !streq(user, "-")) { const char *u = user; - r = get_user_creds(&u, &i->uid, NULL, NULL); + r = get_user_creds(&u, &i->uid, NULL, NULL, NULL); if (r < 0) { log_error("[%s:%u] Unknown user '%s'.", fname, line, user); goto finish; @@ -1078,7 +1127,14 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) { i->type == TRUNCATE_DIRECTORY ? 0755 : 0644; if (age && !streq(age, "-")) { - if (parse_usec(age, &i->age) < 0) { + const char *a = age; + + if (*a == '~') { + i->keep_first_level = true; + a++; + } + + if (parse_usec(a, &i->age) < 0) { log_error("[%s:%u] Invalid age '%s'.", fname, line, age); r = -EBADMSG; goto finish; @@ -1246,6 +1302,30 @@ static int read_config_file(const char *fn, bool ignore_enoent) { return r; } +static char *resolve_fragment(const char *fragment, const char **search_paths) { + const char **p; + char *resolved_path; + + if (is_path(fragment)) + return strdup(fragment); + + STRV_FOREACH(p, search_paths) { + resolved_path = strjoin(*p, "/", fragment, NULL); + if (resolved_path == NULL) { + log_oom(); + return NULL; + } + + if (access(resolved_path, F_OK) == 0) + return resolved_path; + + free(resolved_path); + } + + errno = ENOENT; + return NULL; +} + int main(int argc, char *argv[]) { int r; Item *i; @@ -1267,7 +1347,7 @@ int main(int argc, char *argv[]) { globs = hashmap_new(string_hash_func, string_compare_func); if (!items || !globs) { - log_error("Out of memory"); + log_oom(); r = EXIT_FAILURE; goto finish; } @@ -1277,22 +1357,28 @@ int main(int argc, char *argv[]) { if (optind < argc) { int j; - for (j = optind; j < argc; j++) - if (read_config_file(argv[j], false) < 0) + for (j = optind; j < argc; j++) { + char *fragment; + + fragment = resolve_fragment(argv[j], (const char**) conf_file_dirs); + if (!fragment) { + log_error("Failed to find a %s file: %m", argv[j]); r = EXIT_FAILURE; + goto finish; + } + if (read_config_file(fragment, false) < 0) + r = EXIT_FAILURE; + free(fragment); + } } else { char **files, **f; - r = conf_files_list(&files, ".conf", - "/etc/tmpfiles.d", - "/run/tmpfiles.d", - "/usr/local/lib/tmpfiles.d", - "/usr/lib/tmpfiles.d", - NULL); + r = conf_files_list_strv(&files, ".conf", + (const char **) conf_file_dirs); if (r < 0) { - r = EXIT_FAILURE; log_error("Failed to enumerate tmpfiles.d files: %s", strerror(-r)); + r = EXIT_FAILURE; goto finish; }