+static Hashmap *items = NULL, *globs = NULL;
+static Set *unix_sockets = NULL;
+
+static bool arg_create = false;
+static bool arg_clean = false;
+static bool arg_remove = false;
+
+static const char *arg_prefix = NULL;
+
+#define MAX_DEPTH 256
+
+static bool needs_glob(int t) {
+ return t == IGNORE_PATH || t == REMOVE_PATH || t == RECURSIVE_REMOVE_PATH;
+}
+
+static struct Item* find_glob(Hashmap *h, const char *match) {
+ Item *j;
+ Iterator i;
+
+ HASHMAP_FOREACH(j, h, i)
+ if (fnmatch(j->path, match, FNM_PATHNAME|FNM_PERIOD) == 0)
+ return j;
+
+ return NULL;
+}
+
+static void load_unix_sockets(void) {
+ FILE *f = NULL;
+ char line[LINE_MAX];
+
+ if (unix_sockets)
+ return;
+
+ /* We maintain a cache of the sockets we found in
+ * /proc/net/unix to speed things up a little. */
+
+ if (!(unix_sockets = set_new(string_hash_func, string_compare_func)))
+ return;
+
+ if (!(f = fopen("/proc/net/unix", "re")))
+ return;
+
+ if (!(fgets(line, sizeof(line), f)))
+ goto fail;
+
+ for (;;) {
+ char *p, *s;
+ int k;
+
+ if (!(fgets(line, sizeof(line), f)))
+ break;
+
+ truncate_nl(line);
+
+ if (strlen(line) < 53)
+ continue;
+
+ p = line + 53;
+ p += strspn(p, WHITESPACE);
+ p += strcspn(p, WHITESPACE);
+ p += strspn(p, WHITESPACE);
+
+ if (*p != '/')
+ continue;
+
+ if (!(s = strdup(p)))
+ goto fail;
+
+ path_kill_slashes(s);
+
+ if ((k = set_put(unix_sockets, s)) < 0) {
+ free(s);
+
+ if (k != -EEXIST)
+ goto fail;
+ }
+ }
+
+ return;
+
+fail:
+ set_free_free(unix_sockets);
+ unix_sockets = NULL;
+
+ if (f)
+ fclose(f);
+}
+
+static bool unix_socket_alive(const char *fn) {
+ assert(fn);
+
+ load_unix_sockets();
+
+ if (unix_sockets)
+ return !!set_get(unix_sockets, (char*) fn);
+
+ /* We don't know, so assume yes */
+ return true;
+}
+
+static int dir_cleanup(
+ const char *p,
+ DIR *d,
+ const struct stat *ds,
+ usec_t cutoff,
+ dev_t rootdev,
+ bool mountpoint,
+ int maxdepth)
+{
+ struct dirent *dent;
+ struct timespec times[2];
+ bool deleted = false;
+ char *sub_path = NULL;
+ int r = 0;
+
+ while ((dent = readdir(d))) {
+ struct stat s;
+ usec_t age;
+
+ 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) {
+ log_error("stat(%s/%s) failed: %m", p, dent->d_name);
+ r = -errno;
+ }
+
+ continue;
+ }
+
+ /* Stay on the same filesystem */
+ if (s.st_dev != rootdev)
+ continue;
+
+ /* Do not delete read-only files owned by root */
+ if (s.st_uid == 0 && !(s.st_mode & S_IWUSR))
+ continue;
+
+ free(sub_path);
+ sub_path = NULL;
+
+ if (asprintf(&sub_path, "%s/%s", p, dent->d_name) < 0) {
+ log_error("Out of memory");
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ /* Is there an item configured for this path? */
+ if (hashmap_get(items, sub_path))
+ continue;
+
+ if (find_glob(globs, sub_path))
+ continue;
+
+ if (S_ISDIR(s.st_mode)) {
+
+ if (mountpoint &&
+ streq(dent->d_name, "lost+found") &&
+ s.st_uid == 0)
+ continue;
+
+ if (maxdepth <= 0)
+ log_warning("Reached max depth on %s.", sub_path);
+ else {
+ DIR *sub_dir;
+ int q;
+
+ sub_dir = xopendirat(dirfd(d), dent->d_name, O_NOFOLLOW);
+ if (sub_dir == NULL) {
+ if (errno != ENOENT) {
+ log_error("opendir(%s/%s) failed: %m", p, dent->d_name);
+ r = -errno;
+ }
+
+ continue;
+ }
+
+ q = dir_cleanup(sub_path, sub_dir, &s, cutoff, rootdev, false, maxdepth-1);
+ closedir(sub_dir);
+
+ if (q < 0)
+ r = q;
+ }
+
+ /* Ignore ctime, we change it when deleting */
+ age = MAX(timespec_load(&s.st_mtim),
+ timespec_load(&s.st_atim));
+ if (age >= cutoff)
+ continue;
+
+ log_debug("rmdir '%s'\n", sub_path);
+
+ if (unlinkat(dirfd(d), dent->d_name, AT_REMOVEDIR) < 0) {
+ if (errno != ENOENT && errno != ENOTEMPTY) {
+ log_error("rmdir(%s): %m", sub_path);
+ r = -errno;
+ }
+ }
+
+ } else {
+ /* Skip files for which the sticky bit is
+ * set. These are semantics we define, and are
+ * unknown elsewhere. See XDG_RUNTIME_DIR
+ * specification for details. */
+ if (s.st_mode & S_ISVTX)
+ continue;
+
+ if (mountpoint && S_ISREG(s.st_mode)) {
+ if (streq(dent->d_name, ".journal") &&
+ s.st_uid == 0)
+ continue;
+
+ if (streq(dent->d_name, "aquota.user") ||
+ streq(dent->d_name, "aquota.group"))
+ continue;
+ }
+
+ /* Ignore sockets that are listed in /proc/net/unix */
+ if (S_ISSOCK(s.st_mode) && unix_socket_alive(sub_path))
+ continue;
+
+ /* Ignore device nodes */
+ if (S_ISCHR(s.st_mode) || S_ISBLK(s.st_mode))
+ continue;
+
+ age = MAX3(timespec_load(&s.st_mtim),
+ timespec_load(&s.st_atim),
+ timespec_load(&s.st_ctim));
+
+ if (age >= cutoff)
+ continue;
+
+ log_debug("unlink '%s'\n", sub_path);
+
+ if (unlinkat(dirfd(d), dent->d_name, 0) < 0) {
+ if (errno != ENOENT) {
+ log_error("unlink(%s): %m", sub_path);
+ r = -errno;
+ }
+ }
+
+ deleted = true;
+ }
+ }
+
+finish:
+ if (deleted) {
+ /* Restore original directory timestamps */
+ times[0] = ds->st_atim;
+ times[1] = ds->st_mtim;
+
+ if (futimens(dirfd(d), times) < 0)
+ log_error("utimensat(%s): %m", p);
+ }
+
+ free(sub_path);
+
+ return r;
+}
+
+static int clean_item(Item *i) {
+ DIR *d;
+ struct stat s, ps;
+ bool mountpoint;
+ int r;
+ usec_t cutoff, n;
+
+ assert(i);
+
+ if (i->type != CREATE_DIRECTORY &&
+ i->type != TRUNCATE_DIRECTORY &&
+ i->type != IGNORE_PATH)
+ return 0;
+
+ if (!i->age_set || i->age <= 0)
+ return 0;
+
+ n = now(CLOCK_REALTIME);
+ if (n < i->age)
+ return 0;
+
+ cutoff = n - i->age;
+
+ d = opendir(i->path);
+ if (!d) {
+ if (errno == ENOENT)
+ return 0;
+
+ log_error("Failed to open directory %s: %m", i->path);
+ return -errno;
+ }
+
+ if (fstat(dirfd(d), &s) < 0) {
+ log_error("stat(%s) failed: %m", i->path);
+ r = -errno;