#include "log.h"
#include "util.h"
#include "macro.h"
+#include "missing.h"
#include "mkdir.h"
#include "path-util.h"
#include "strv.h"
#include "set.h"
#include "conf-files.h"
#include "capability.h"
+#include "specifier.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
CREATE_SYMLINK = 'L',
CREATE_CHAR_DEVICE = 'c',
CREATE_BLOCK_DEVICE = 'b',
+ ADJUST_MODE = 'm',
/* These ones take globs */
IGNORE_PATH = 'x',
static bool arg_clean = false;
static bool arg_remove = false;
-static const char *arg_prefix = NULL;
+static char **include_prefixes = NULL;
+static char **exclude_prefixes = NULL;
static const char conf_file_dirs[] =
"/etc/tmpfiles.d\0"
}
static void load_unix_sockets(void) {
- FILE _cleanup_fclose_ *f = NULL;
+ _cleanup_fclose_ FILE *f = NULL;
char line[LINE_MAX];
if (unix_sockets)
path_kill_slashes(s);
- k = set_put(unix_sockets, s);
- if (k < 0) {
- free(s);
-
- if (k != -EEXIST)
- goto fail;
- }
+ k = set_consume(unix_sockets, s);
+ if (k < 0 && k != -EEXIST)
+ goto fail;
}
return;
dev_t rootdev,
bool mountpoint,
int maxdepth,
- bool keep_this_level)
-{
+ bool keep_this_level) {
+
struct dirent *dent;
struct timespec times[2];
bool deleted = false;
while ((dent = readdir(d))) {
struct stat s;
usec_t age;
- char _cleanup_free_ *sub_path = NULL;
+ _cleanup_free_ char *sub_path = NULL;
if (streq(dent->d_name, ".") ||
streq(dent->d_name, ".."))
continue;
if (fstatat(dirfd(d), dent->d_name, &s, AT_SYMLINK_NOFOLLOW) < 0) {
+ if (errno == ENOENT)
+ continue;
- if (errno != ENOENT) {
+ /* FUSE, NFS mounts, SELinux might return EACCES */
+ if (errno == EACCES)
+ log_debug("stat(%s/%s) failed: %m", p, dent->d_name);
+ else
log_error("stat(%s/%s) failed: %m", p, dent->d_name);
- r = -errno;
- }
-
+ r = -errno;
continue;
}
if (maxdepth <= 0)
log_warning("Reached max depth on %s.", sub_path);
else {
- DIR _cleanup_closedir_ *sub_dir;
+ _cleanup_closedir_ DIR *sub_dir;
int q;
sub_dir = xopendirat(dirfd(d), dent->d_name, O_NOFOLLOW|O_NOATIME);
return r;
}
-static int item_set_perms(Item *i, const char *path) {
+static int item_set_perms_full(Item *i, const char *path, bool ignore_enoent) {
+ int r;
+
/* not using i->path directly because it may be a glob */
if (i->mode_set)
if (chmod(path, i->mode) < 0) {
- log_error("chmod(%s) failed: %m", path);
- return -errno;
+ if (errno != ENOENT || !ignore_enoent) {
+ log_error("chmod(%s) failed: %m", path);
+ return -errno;
+ }
}
if (i->uid_set || i->gid_set)
i->uid_set ? i->uid : (uid_t) -1,
i->gid_set ? i->gid : (gid_t) -1) < 0) {
- log_error("chown(%s) failed: %m", path);
- return -errno;
+ if (errno != ENOENT || !ignore_enoent) {
+ log_error("chown(%s) failed: %m", path);
+ return -errno;
+ }
}
- return label_fix(path, false, false);
+ r = label_fix(path, false, false);
+ return r == -ENOENT && ignore_enoent ? 0 : r;
+}
+
+static int item_set_perms(Item *i, const char *path) {
+ return item_set_perms_full(i, path, 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;
+ RUN_WITH_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();
+ errno = e;
+ }
if (fd < 0) {
if (i->type == WRITE_FILE && errno == ENOENT)
}
static int recursive_relabel_children(Item *i, const char *path) {
- DIR _cleanup_closedir_ *d;
+ _cleanup_closedir_ DIR *d;
int ret = 0;
/* This returns the first error we run into, but nevertheless
union dirent_storage buf;
bool is_dir;
int r;
- char _cleanup_free_ *entry_path = NULL;
+ _cleanup_free_ char *entry_path = NULL;
r = readdir_r(d, &buf.de, &de);
if (r != 0) {
static int glob_item(Item *i, int (*action)(Item *, const char *)) {
int r = 0, k;
- glob_t g;
+ _cleanup_globfree_ glob_t g = {};
char **fn;
- zero(g);
-
errno = 0;
- if ((k = glob(i->path, GLOB_NOSORT|GLOB_BRACE, NULL, &g)) != 0) {
-
+ k = glob(i->path, GLOB_NOSORT|GLOB_BRACE, NULL, &g);
+ if (k != 0)
if (k != GLOB_NOMATCH) {
- if (errno != 0)
+ if (errno > 0)
errno = EIO;
log_error("glob(%s) failed: %m", i->path);
return -errno;
}
- }
- STRV_FOREACH(fn, g.gl_pathv)
- if ((k = action(i, *fn)) < 0)
+ STRV_FOREACH(fn, g.gl_pathv) {
+ k = action(i, *fn);
+ if (k < 0)
r = k;
+ }
- globfree(&g);
return r;
}
static int create_item(Item *i) {
int r, e;
- mode_t u;
struct stat st;
assert(i);
if (r < 0)
return r;
break;
+
case WRITE_FILE:
r = glob_item(i, write_one_file);
if (r < 0)
break;
+ case ADJUST_MODE:
+ r = item_set_perms_full(i, i->path, true);
+ if (r < 0)
+ return r;
+
+ break;
+
case TRUNCATE_DIRECTORY:
case CREATE_DIRECTORY:
- u = umask(0);
- mkdir_parents_label(i->path, 0755);
- r = mkdir(i->path, i->mode);
- umask(u);
+ RUN_WITH_UMASK(0000) {
+ mkdir_parents_label(i->path, 0755);
+ r = mkdir(i->path, i->mode);
+ }
if (r < 0 && errno != EEXIST) {
log_error("Failed to create directory %s: %m", i->path);
case CREATE_FIFO:
- u = umask(0);
- r = mkfifo(i->path, i->mode);
- umask(u);
+ RUN_WITH_UMASK(0000) {
+ r = mkfifo(i->path, i->mode);
+ }
if (r < 0 && errno != EEXIST) {
log_error("Failed to create fifo %s: %m", i->path);
if (have_effective_cap(CAP_MKNOD) == 0) {
/* In a container we lack CAP_MKNOD. We
- shouldnt attempt to create the device node in
+ shouldn't attempt to create the device node in
that case to avoid noise, and we don't support
virtualized devices in containers anyway. */
file_type = (i->type == CREATE_BLOCK_DEVICE ? S_IFBLK : S_IFCHR);
- u = umask(0);
- 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);
- errno = e;
+ RUN_WITH_UMASK(0000) {
+ label_context_set(i->path, file_type);
+ r = mknod(i->path, i->mode | file_type, i->major_minor);
+ e = errno;
+ label_context_clear();
+ errno = e;
+ }
if (r < 0 && errno != EEXIST) {
log_error("Failed to create device node %s: %m", i->path);
r = glob_item(i, item_set_perms);
if (r < 0)
- return 0;
+ return r;
break;
case RECURSIVE_RELABEL_PATH:
case RELABEL_PATH:
case RECURSIVE_RELABEL_PATH:
case WRITE_FILE:
+ case ADJUST_MODE:
break;
case REMOVE_PATH:
case RELABEL_PATH:
case RECURSIVE_RELABEL_PATH:
case WRITE_FILE:
+ case ADJUST_MODE:
break;
case REMOVE_PATH:
}
static int clean_item_instance(Item *i, const char* instance) {
- DIR _cleanup_closedir_ *d = NULL;
+ _cleanup_closedir_ DIR *d = NULL;
struct stat s, ps;
bool mountpoint;
int r;
free(i);
}
+DEFINE_TRIVIAL_CLEANUP_FUNC(Item*, item_free);
+#define _cleanup_item_free_ _cleanup_(item_freep)
+
static bool item_equal(Item *a, Item *b) {
assert(a);
assert(b);
return true;
}
+static bool should_include_path(const char *path) {
+ char **prefix;
+
+ STRV_FOREACH(prefix, exclude_prefixes) {
+ if (path_startswith(path, *prefix))
+ return false;
+ }
+
+ STRV_FOREACH(prefix, include_prefixes) {
+ if (path_startswith(path, *prefix))
+ return true;
+ }
+
+ /* no matches, so we should include this path only if we
+ * have no whitelist at all */
+ return strv_length(include_prefixes) == 0;
+}
+
static int parse_line(const char *fname, unsigned line, const char *buffer) {
- Item *i, *existing;
- char _cleanup_free_
- *mode = NULL, *user = NULL, *group = NULL, *age = NULL;
+
+ static const Specifier specifier_table[] = {
+ { 'm', specifier_machine_id, NULL },
+ { 'b', specifier_boot_id, NULL },
+ { 'H', specifier_host_name, NULL },
+ { 'v', specifier_kernel_release, NULL },
+ {}
+ };
+
+ _cleanup_item_free_ Item *i = NULL;
+ Item *existing;
+ _cleanup_free_ char
+ *mode = NULL, *user = NULL, *group = NULL, *age = NULL, *path = NULL;
char type;
Hashmap *h;
int r, n = -1;
assert(line >= 1);
assert(buffer);
- i = new0(Item, 1);
- if (!i)
- return log_oom();
-
r = sscanf(buffer,
"%c %ms %ms %ms %ms %ms %n",
&type,
- &i->path,
+ &path,
&mode,
&user,
&group,
&n);
if (r < 2) {
log_error("[%s:%u] Syntax error.", fname, line);
- r = -EIO;
- goto finish;
+ return -EIO;
+ }
+
+ i = new0(Item, 1);
+ if (!i)
+ return log_oom();
+
+ r = specifier_printf(path, specifier_table, NULL, &i->path);
+ if (r < 0) {
+ log_error("[%s:%u] Failed to replace specifiers: %s", fname, line, path);
+ return r;
}
if (n >= 0) {
case RECURSIVE_REMOVE_PATH:
case RELABEL_PATH:
case RECURSIVE_RELABEL_PATH:
+ case ADJUST_MODE:
break;
case CREATE_SYMLINK:
if (!i->argument) {
log_error("[%s:%u] Symlink file requires argument.", fname, line);
- r = -EBADMSG;
- goto finish;
+ return -EBADMSG;
}
break;
case WRITE_FILE:
if (!i->argument) {
log_error("[%s:%u] Write file requires argument.", fname, line);
- r = -EBADMSG;
- goto finish;
+ return -EBADMSG;
}
break;
if (!i->argument) {
log_error("[%s:%u] Device file requires argument.", fname, line);
- r = -EBADMSG;
- goto finish;
+ return -EBADMSG;
}
if (sscanf(i->argument, "%u:%u", &major, &minor) != 2) {
log_error("[%s:%u] Can't parse device file major/minor '%s'.", fname, line, i->argument);
- r = -EBADMSG;
- goto finish;
+ return -EBADMSG;
}
i->major_minor = makedev(major, minor);
default:
log_error("[%s:%u] Unknown file type '%c'.", fname, line, type);
- r = -EBADMSG;
- goto finish;
+ return -EBADMSG;
}
i->type = type;
if (!path_is_absolute(i->path)) {
log_error("[%s:%u] Path '%s' not absolute.", fname, line, i->path);
- r = -EBADMSG;
- goto finish;
+ return -EBADMSG;
}
path_kill_slashes(i->path);
- if (arg_prefix && !path_startswith(i->path, arg_prefix)) {
- r = 0;
- goto finish;
- }
+ if (!should_include_path(i->path))
+ return 0;
if (user && !streq(user, "-")) {
const char *u = user;
r = get_user_creds(&u, &i->uid, NULL, NULL, NULL);
if (r < 0) {
log_error("[%s:%u] Unknown user '%s'.", fname, line, user);
- goto finish;
+ return r;
}
i->uid_set = true;
r = get_group_creds(&g, &i->gid);
if (r < 0) {
log_error("[%s:%u] Unknown group '%s'.", fname, line, group);
- goto finish;
+ return r;
}
i->gid_set = true;
if (sscanf(mode, "%o", &m) != 1) {
log_error("[%s:%u] Invalid mode '%s'.", fname, line, mode);
- r = -ENOENT;
- goto finish;
+ return -ENOENT;
}
i->mode = m;
a++;
}
- if (parse_usec(a, &i->age) < 0) {
+ if (parse_sec(a, &i->age) < 0) {
log_error("[%s:%u] Invalid age '%s'.", fname, line, age);
- r = -EBADMSG;
- goto finish;
+ return -EBADMSG;
}
i->age_set = true;
if (!item_equal(existing, i))
log_warning("Two or more conflicting lines for %s configured, ignoring.", i->path);
- r = 0;
- goto finish;
+ return 0;
}
r = hashmap_put(h, i->path, i);
if (r < 0) {
log_error("Failed to insert item %s: %s", i->path, strerror(-r));
- goto finish;
+ return r;
}
- i = NULL;
- r = 0;
+ i = NULL; /* avoid cleanup */
-finish:
- if (i)
- item_free(i);
-
- return r;
+ return 0;
}
static int help(void) {
printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n"
"Creates, deletes and cleans up volatile and temporary files and directories.\n\n"
- " -h --help Show this help\n"
- " --create Create marked files/directories\n"
- " --clean Clean up marked directories\n"
- " --remove Remove marked files/directories\n"
- " --prefix=PATH Only apply rules that apply to paths with the specified prefix\n",
+ " -h --help Show this help\n"
+ " --create Create marked files/directories\n"
+ " --clean Clean up marked directories\n"
+ " --remove Remove marked files/directories\n"
+ " --prefix=PATH Only apply rules that apply to paths with the specified prefix\n"
+ " --exclude-prefix=PATH Ignore rules that apply to paths with the specified prefix\n",
program_invocation_short_name);
return 0;
ARG_CREATE,
ARG_CLEAN,
ARG_REMOVE,
- ARG_PREFIX
+ ARG_PREFIX,
+ ARG_EXCLUDE_PREFIX,
};
static const struct option options[] = {
- { "help", no_argument, NULL, 'h' },
- { "create", no_argument, NULL, ARG_CREATE },
- { "clean", no_argument, NULL, ARG_CLEAN },
- { "remove", no_argument, NULL, ARG_REMOVE },
- { "prefix", required_argument, NULL, ARG_PREFIX },
- { NULL, 0, NULL, 0 }
+ { "help", no_argument, NULL, 'h' },
+ { "create", no_argument, NULL, ARG_CREATE },
+ { "clean", no_argument, NULL, ARG_CLEAN },
+ { "remove", no_argument, NULL, ARG_REMOVE },
+ { "prefix", required_argument, NULL, ARG_PREFIX },
+ { "exclude-prefix", required_argument, NULL, ARG_EXCLUDE_PREFIX },
+ { NULL, 0, NULL, 0 }
};
int c;
break;
case ARG_PREFIX:
- arg_prefix = optarg;
+ if (strv_extend(&include_prefixes, optarg) < 0)
+ return log_oom();
+ break;
+
+ case ARG_EXCLUDE_PREFIX:
+ if (strv_extend(&exclude_prefixes, optarg) < 0)
+ return log_oom();
break;
case '?':
}
static int read_config_file(const char *fn, bool ignore_enoent) {
- FILE *f;
- unsigned v = 0;
- int r;
+ _cleanup_fclose_ FILE *f = NULL;
+ char line[LINE_MAX];
Iterator iterator;
+ unsigned v = 0;
Item *i;
+ int r;
assert(fn);
return r;
}
- log_debug("apply: %s\n", fn);
- for (;;) {
- char line[LINE_MAX], *l;
+ FOREACH_LINE(line, f, break) {
+ char *l;
int k;
- if (!(fgets(line, sizeof(line), f)))
- break;
-
v++;
l = strstrip(line);
if (*l == '#' || *l == 0)
continue;
- if ((k = parse_line(fn, v, l)) < 0)
- if (r == 0)
- r = k;
+ k = parse_line(fn, v, l);
+ if (k < 0 && r == 0)
+ r = k;
}
/* we have to determine age parameter for each entry of type X */
r = -EIO;
}
- fclose(f);
-
return r;
}
hashmap_free(items);
hashmap_free(globs);
+ strv_free(include_prefixes);
+
set_free_free(unix_sockets);
label_finish();