tmpfiles: Add '=' action modifier.

Add the '=' action modifier that instructs tmpfiles.d to check the file
type of a path and remove objects that do not match before trying to
open or create the path.

BUG=chromium:1186405
TEST=manually checked.

Change-Id: If807dc0db427393e9e0047aba640d0d114897c26
diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c
index 9906c70..bba20eb 100644
--- a/src/tmpfiles/tmpfiles.c
+++ b/src/tmpfiles/tmpfiles.c
@@ -139,6 +139,8 @@
 
         bool allow_failure:1;
 
+        bool try_replace:1;
+
         OperationMask done;
 } Item;
 
@@ -1992,6 +1994,64 @@
         return r;
 }
 
+static int rm_if_wrong_type(mode_t mode, const char *path, bool follow) {
+        struct stat st;
+        int r;
+
+        assert(path);
+        assert((mode & ~S_IFMT) == 0);
+        log_debug("Checking type of %s", path);
+
+        if (follow)
+                r = stat(path, &st);
+        else
+                r = lstat(path, &st);
+        if (r < 0) {
+                if (errno != ENOENT) {
+                        log_error_errno(r, "stat(%s): %m", path);
+                }
+                return -errno;
+        }
+
+        if ((st.st_mode & S_IFMT) == mode)
+                return 0;
+
+        log_warning("wrong file type 0x%x; rm -rf \"%s\"", st.st_mode & S_IFMT, path);
+        r = rm_rf(path, REMOVE_ROOT|REMOVE_SUBVOLUME|REMOVE_PHYSICAL);
+        if (r < 0) {
+                if (errno != ENOENT) {
+                        log_error_errno(r, "rm_rf(%s): %m", path);
+                }
+                return -errno;
+        }
+
+        return 0;
+}
+
+static int rm_nondir_parents(const char *path) {
+        char *p, *e;
+        int r = -ENOENT;
+
+        assert(path);
+
+        // Walk up the path components until one is found with the expected type or there are no more.
+        p = strdupa(path);
+        // If the path doesn't exist, check the next path component.
+        while (r == -ENOENT || r == -ENOTDIR) {
+                e = strrchr(p, '/');
+                if (!e)
+                        return 0;
+
+                *e = 0;
+                r = rm_if_wrong_type(S_IFDIR, p, true);
+                // Remove dangling symlinks.
+                if (r == -ENOENT) {
+                        r = rm_if_wrong_type(S_IFDIR, p, false);
+                }
+        }
+        return r;
+}
+
 static int create_item(Item *i) {
         CreationMode creation;
         int r = 0;
@@ -2010,9 +2070,21 @@
 
         case TRUNCATE_FILE:
         case CREATE_FILE:
+                if (i->try_replace) {
+                        r = rm_nondir_parents(i->path);
+                        if (r < 0)
+                                return r;
+                }
+
                 RUN_WITH_UMASK(0000)
                         (void) mkdir_parents_label(i->path, 0755);
 
+                if (i->try_replace) {
+                        r = rm_if_wrong_type(S_IFREG, i->path, false);
+                        if (r < 0 && r != -ENOENT)
+                                return r;
+                }
+
                 if ((i->type == CREATE_FILE && i->append_or_force) || i->type == TRUNCATE_FILE)
                         r = truncate_file(i, i->path);
                 else
@@ -2023,6 +2095,12 @@
                 break;
 
         case COPY_FILES:
+                if (i->try_replace) {
+                        r = rm_nondir_parents(i->path);
+                        if (r < 0)
+                                return r;
+                }
+
                 RUN_WITH_UMASK(0000)
                         (void) mkdir_parents_label(i->path, 0755);
 
@@ -2040,9 +2118,21 @@
 
         case CREATE_DIRECTORY:
         case TRUNCATE_DIRECTORY:
+                if (i->try_replace) {
+                        r = rm_nondir_parents(i->path);
+                        if (r < 0)
+                                return r;
+                }
+
                 RUN_WITH_UMASK(0000)
                         (void) mkdir_parents_label(i->path, 0755);
 
+                if (i->try_replace) {
+                        r = rm_if_wrong_type(S_IFDIR, i->path, false);
+                        if (r < 0 && r != -ENOENT)
+                                return r;
+                }
+
                 r = create_directory(i, i->path);
                 if (r < 0)
                         return r;
@@ -2051,9 +2141,21 @@
         case CREATE_SUBVOLUME:
         case CREATE_SUBVOLUME_INHERIT_QUOTA:
         case CREATE_SUBVOLUME_NEW_QUOTA:
+                if (i->try_replace) {
+                        r = rm_nondir_parents(i->path);
+                        if (r < 0)
+                                return r;
+                }
+
                 RUN_WITH_UMASK(0000)
                         (void) mkdir_parents_label(i->path, 0755);
 
+                if (i->try_replace) {
+                        r = rm_if_wrong_type(S_IFDIR, i->path, false);
+                        if (r < 0 && r != -ENOENT)
+                                return r;
+                }
+
                 r = create_subvolume(i, i->path);
                 if (r < 0)
                         return r;
@@ -2066,18 +2168,42 @@
                 break;
 
         case CREATE_FIFO:
+                if (i->try_replace) {
+                        r = rm_nondir_parents(i->path);
+                        if (r < 0)
+                                return r;
+                }
+
                 RUN_WITH_UMASK(0000)
                         (void) mkdir_parents_label(i->path, 0755);
 
+                if (i->try_replace) {
+                        r = rm_if_wrong_type(S_IFIFO, i->path, false);
+                        if (r < 0 && r != -ENOENT)
+                                return r;
+                }
+
                 r = create_fifo(i, i->path);
                 if (r < 0)
                         return r;
                 break;
 
         case CREATE_SYMLINK: {
+                if (i->try_replace) {
+                        r = rm_nondir_parents(i->path);
+                        if (r < 0)
+                                return r;
+                }
+
                 RUN_WITH_UMASK(0000)
                         (void) mkdir_parents_label(i->path, 0755);
 
+                if (i->try_replace) {
+                        r = rm_if_wrong_type(S_IFLNK, i->path, false);
+                        if (r < 0 && r != -ENOENT)
+                                return r;
+                }
+
                 mac_selinux_create_file_prepare(i->path, S_IFLNK);
                 r = symlink(i->argument, i->path);
                 mac_selinux_create_file_clear();
@@ -2132,9 +2258,21 @@
                         return 0;
                 }
 
+                if (i->try_replace) {
+                        r = rm_nondir_parents(i->path);
+                        if (r < 0)
+                                return r;
+                }
+
                 RUN_WITH_UMASK(0000)
                         (void) mkdir_parents_label(i->path, 0755);
 
+                if (i->try_replace) {
+                        r = rm_if_wrong_type(i->type == CREATE_BLOCK_DEVICE ? S_IFBLK : S_IFCHR, i->path, false);
+                        if (r < 0 && r != -ENOENT)
+                                return r;
+                }
+
                 r = create_device(i, i->type == CREATE_BLOCK_DEVICE ? S_IFBLK : S_IFCHR);
                 if (r < 0)
                         return r;
@@ -2633,7 +2771,7 @@
         ItemArray *existing;
         OrderedHashmap *h;
         int r, pos;
-        bool append_or_force = false, boot = false, allow_failure = false;
+        bool append_or_force = false, boot = false, allow_failure = false, try_replace = false;
 
         assert(fname);
         assert(line >= 1);
@@ -2678,6 +2816,8 @@
                         append_or_force = true;
                 else if (action[pos] == '-' && !allow_failure)
                         allow_failure = true;
+                else if (action[pos] == '=' && !try_replace)
+                        try_replace = true;
                 else {
                         *invalid_config = true;
                         return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), "Unknown modifiers in command '%s'", action);
@@ -2692,6 +2832,7 @@
         i.type = action[0];
         i.append_or_force = append_or_force;
         i.allow_failure = allow_failure;
+        i.try_replace = try_replace;
 
         r = specifier_printf(path, specifier_table, NULL, &i.path);
         if (r == -ENXIO)