chiark / gitweb /
util: rework cunescape(), improve error handling
[elogind.git] / src / shared / util.c
index 8b76531d4f6072f0bc89f27e141d41901f3ce915..a8e485cc9c805f9647417fc23bbcf692cfc60c8a 100644 (file)
@@ -151,6 +151,27 @@ char* endswith(const char *s, const char *postfix) {
         return (char*) s + sl - pl;
 }
 
+char* endswith_no_case(const char *s, const char *postfix) {
+        size_t sl, pl;
+
+        assert(s);
+        assert(postfix);
+
+        sl = strlen(s);
+        pl = strlen(postfix);
+
+        if (pl == 0)
+                return (char*) s + sl;
+
+        if (sl < pl)
+                return NULL;
+
+        if (strcasecmp(s + sl - pl, postfix) != 0)
+                return NULL;
+
+        return (char*) s + sl - pl;
+}
+
 char* first_word(const char *s, const char *word) {
         size_t sl, wl;
         const char *p;
@@ -1466,12 +1487,13 @@ static int cunescape_one(const char *p, size_t length, char *ret) {
         return r;
 }
 
-char *cunescape_length_with_prefix(const char *s, size_t length, const char *prefix) {
+int cunescape_length_with_prefix(const char *s, size_t length, const char *prefix, UnescapeFlags flags, char **ret) {
         char *r, *t;
         const char *f;
         size_t pl;
 
         assert(s);
+        assert(ret);
 
         /* Undoes C style string escaping, and optionally prefixes it. */
 
@@ -1479,7 +1501,7 @@ char *cunescape_length_with_prefix(const char *s, size_t length, const char *pre
 
         r = new(char, pl+length+1);
         if (!r)
-                return NULL;
+                return -ENOMEM;
 
         if (prefix)
                 memcpy(r, prefix, pl);
@@ -1491,17 +1513,31 @@ char *cunescape_length_with_prefix(const char *s, size_t length, const char *pre
                 remaining = s + length - f;
                 assert(remaining > 0);
 
-                if (*f != '\\' || remaining == 1) {
-                        /* a literal literal, or a trailing backslash, copy verbatim */
+                if (*f != '\\') {
+                        /* A literal literal, copy verbatim */
                         *(t++) = *f;
                         continue;
                 }
 
+                if (remaining == 1) {
+                        if (flags & UNESCAPE_RELAX) {
+                                /* A trailing backslash, copy verbatim */
+                                *(t++) = *f;
+                                continue;
+                        }
+
+                        return -EINVAL;
+                }
+
                 k = cunescape_one(f + 1, remaining - 1, t);
                 if (k < 0) {
-                        /* Invalid escape code, let's take it literal then */
-                        *(t++) = '\\';
-                        continue;
+                        if (flags & UNESCAPE_RELAX) {
+                                /* Invalid escape code, let's take it literal then */
+                                *(t++) = '\\';
+                                continue;
+                        }
+
+                        return k;
                 }
 
                 f += k;
@@ -1509,17 +1545,17 @@ char *cunescape_length_with_prefix(const char *s, size_t length, const char *pre
         }
 
         *t = 0;
-        return r;
-}
 
-char *cunescape_length(const char *s, size_t length) {
-        return cunescape_length_with_prefix(s, length, NULL);
+        *ret = r;
+        return t - r;
 }
 
-char *cunescape(const char *s) {
-        assert(s);
+int cunescape_length(const char *s, size_t length, UnescapeFlags flags, char **ret) {
+        return cunescape_length_with_prefix(s, length, NULL, flags, ret);
+}
 
-        return cunescape_length(s, strlen(s));
+int cunescape(const char *s, UnescapeFlags flags, char **ret) {
+        return cunescape_length(s, strlen(s), flags, ret);
 }
 
 char *xescape(const char *s, const char *bad) {
@@ -2882,7 +2918,7 @@ int getttyname_malloc(int fd, char **ret) {
 
 int getttyname_harder(int fd, char **r) {
         int k;
-        char *s;
+        char *s = NULL;
 
         k = getttyname_malloc(fd, &s);
         if (k < 0)
@@ -2988,101 +3024,14 @@ int get_ctty(pid_t pid, dev_t *_devnr, char **r) {
         return 0;
 }
 
-int rm_rf_children_dangerous(int fd, bool only_dirs, bool honour_sticky, struct stat *root_dev) {
-        _cleanup_closedir_ DIR *d = NULL;
-        int ret = 0;
-
-        assert(fd >= 0);
-
-        /* This returns the first error we run into, but nevertheless
-         * tries to go on. This closes the passed fd. */
-
-        d = fdopendir(fd);
-        if (!d) {
-                safe_close(fd);
-
-                return errno == ENOENT ? 0 : -errno;
-        }
-
-        for (;;) {
-                struct dirent *de;
-                bool is_dir, keep_around;
-                struct stat st;
-                int r;
-
-                errno = 0;
-                de = readdir(d);
-                if (!de) {
-                        if (errno != 0 && ret == 0)
-                                ret = -errno;
-                        return ret;
-                }
-
-                if (streq(de->d_name, ".") || streq(de->d_name, ".."))
-                        continue;
-
-                if (de->d_type == DT_UNKNOWN ||
-                    honour_sticky ||
-                    (de->d_type == DT_DIR && root_dev)) {
-                        if (fstatat(fd, de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) {
-                                if (ret == 0 && errno != ENOENT)
-                                        ret = -errno;
-                                continue;
-                        }
-
-                        is_dir = S_ISDIR(st.st_mode);
-                        keep_around =
-                                honour_sticky &&
-                                (st.st_uid == 0 || st.st_uid == getuid()) &&
-                                (st.st_mode & S_ISVTX);
-                } else {
-                        is_dir = de->d_type == DT_DIR;
-                        keep_around = false;
-                }
-
-                if (is_dir) {
-                        int subdir_fd;
-
-                        /* if root_dev is set, remove subdirectories only, if device is same as dir */
-                        if (root_dev && st.st_dev != root_dev->st_dev)
-                                continue;
-
-                        subdir_fd = openat(fd, de->d_name,
-                                           O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME);
-                        if (subdir_fd < 0) {
-                                if (ret == 0 && errno != ENOENT)
-                                        ret = -errno;
-                                continue;
-                        }
-
-                        r = rm_rf_children_dangerous(subdir_fd, only_dirs, honour_sticky, root_dev);
-                        if (r < 0 && ret == 0)
-                                ret = r;
-
-                        if (!keep_around)
-                                if (unlinkat(fd, de->d_name, AT_REMOVEDIR) < 0) {
-                                        if (ret == 0 && errno != ENOENT)
-                                                ret = -errno;
-                                }
-
-                } else if (!only_dirs && !keep_around) {
-
-                        if (unlinkat(fd, de->d_name, 0) < 0) {
-                                if (ret == 0 && errno != ENOENT)
-                                        ret = -errno;
-                        }
-                }
-        }
-}
-
-_pure_ static int is_temporary_fs(struct statfs *s) {
+bool is_temporary_fs(const struct statfs *s) {
         assert(s);
 
         return F_TYPE_EQUAL(s->f_type, TMPFS_MAGIC) ||
                F_TYPE_EQUAL(s->f_type, RAMFS_MAGIC);
 }
 
-int is_fd_on_temporary_fs(int fd) {
+int fd_is_temporary_fs(int fd) {
         struct statfs s;
 
         if (fstatfs(fd, &s) < 0)
@@ -3091,114 +3040,6 @@ int is_fd_on_temporary_fs(int fd) {
         return is_temporary_fs(&s);
 }
 
-int rm_rf_children(int fd, bool only_dirs, bool honour_sticky, struct stat *root_dev) {
-        struct statfs s;
-
-        assert(fd >= 0);
-
-        if (fstatfs(fd, &s) < 0) {
-                safe_close(fd);
-                return -errno;
-        }
-
-        /* We refuse to clean disk file systems with this call. This
-         * is extra paranoia just to be sure we never ever remove
-         * non-state data */
-        if (!is_temporary_fs(&s)) {
-                log_error("Attempted to remove disk file system, and we can't allow that.");
-                safe_close(fd);
-                return -EPERM;
-        }
-
-        return rm_rf_children_dangerous(fd, only_dirs, honour_sticky, root_dev);
-}
-
-static int file_is_priv_sticky(const char *p) {
-        struct stat st;
-
-        assert(p);
-
-        if (lstat(p, &st) < 0)
-                return -errno;
-
-        return
-                (st.st_uid == 0 || st.st_uid == getuid()) &&
-                (st.st_mode & S_ISVTX);
-}
-
-static int rm_rf_internal(const char *path, bool only_dirs, bool delete_root, bool honour_sticky, bool dangerous) {
-        int fd, r;
-        struct statfs s;
-
-        assert(path);
-
-        /* We refuse to clean the root file system with this
-         * call. This is extra paranoia to never cause a really
-         * seriously broken system. */
-        if (path_equal(path, "/")) {
-                log_error("Attempted to remove entire root file system, and we can't allow that.");
-                return -EPERM;
-        }
-
-        fd = open(path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME);
-        if (fd < 0) {
-
-                if (errno != ENOTDIR && errno != ELOOP)
-                        return -errno;
-
-                if (!dangerous) {
-                        if (statfs(path, &s) < 0)
-                                return -errno;
-
-                        if (!is_temporary_fs(&s)) {
-                                log_error("Attempted to remove disk file system, and we can't allow that.");
-                                return -EPERM;
-                        }
-                }
-
-                if (delete_root && !only_dirs)
-                        if (unlink(path) < 0 && errno != ENOENT)
-                                return -errno;
-
-                return 0;
-        }
-
-        if (!dangerous) {
-                if (fstatfs(fd, &s) < 0) {
-                        safe_close(fd);
-                        return -errno;
-                }
-
-                if (!is_temporary_fs(&s)) {
-                        log_error("Attempted to remove disk file system, and we can't allow that.");
-                        safe_close(fd);
-                        return -EPERM;
-                }
-        }
-
-        r = rm_rf_children_dangerous(fd, only_dirs, honour_sticky, NULL);
-        if (delete_root) {
-
-                if (honour_sticky && file_is_priv_sticky(path) > 0)
-                        return r;
-
-                if (rmdir(path) < 0 && errno != ENOENT) {
-                        if (r == 0)
-                                r = -errno;
-                }
-        }
-
-        return r;
-}
-
-int rm_rf(const char *path, bool only_dirs, bool delete_root, bool honour_sticky) {
-        return rm_rf_internal(path, only_dirs, delete_root, honour_sticky, false);
-}
-
-int rm_rf_dangerous(const char *path, bool only_dirs, bool delete_root, bool honour_sticky) {
-        return rm_rf_internal(path, only_dirs, delete_root, honour_sticky, true);
-}
-
 int chmod_and_chown(const char *path, mode_t mode, uid_t uid, gid_t gid) {
         assert(path);
 
@@ -3435,7 +3276,7 @@ char **replace_env_argv(char **argv, char **env) {
                 /* If $FOO appears as single word, replace it by the split up variable */
                 if ((*i)[0] == '$' && (*i)[1] != '{') {
                         char *e;
-                        char **w, **m;
+                        char **w, **m = NULL;
                         unsigned q;
 
                         e = strv_env_get(env, *i+1);
@@ -6926,9 +6767,9 @@ int umount_recursive(const char *prefix, int flags) {
                                 continue;
                         }
 
-                        p = cunescape(path);
-                        if (!p)
-                                return -ENOMEM;
+                        r = cunescape(path, UNESCAPE_RELAX, &p);
+                        if (r < 0)
+                                return r;
 
                         if (!path_startswith(p, prefix))
                                 continue;
@@ -7028,9 +6869,9 @@ int bind_remount_recursive(const char *prefix, bool ro) {
                                 continue;
                         }
 
-                        p = cunescape(path);
-                        if (!p)
-                                return -ENOMEM;
+                        r = cunescape(path, UNESCAPE_RELAX, &p);
+                        if (r < 0)
+                                return r;
 
                         /* Let's ignore autofs mounts.  If they aren't
                          * triggered yet, we want to avoid triggering
@@ -7794,71 +7635,6 @@ int fd_setcrtime(int fd, usec_t usec) {
         return 0;
 }
 
-int same_fd(int a, int b) {
-        struct stat sta, stb;
-        pid_t pid;
-        int r, fa, fb;
-
-        assert(a >= 0);
-        assert(b >= 0);
-
-        /* Compares two file descriptors. Note that semantics are
-         * quite different depending on whether we have kcmp() or we
-         * don't. If we have kcmp() this will only return true for
-         * dup()ed file descriptors, but not otherwise. If we don't
-         * have kcmp() this will also return true for two fds of the same
-         * file, created by separate open() calls. Since we use this
-         * call mostly for filtering out duplicates in the fd store
-         * this difference hopefully doesn't matter too much. */
-
-        if (a == b)
-                return true;
-
-        /* Try to use kcmp() if we have it. */
-        pid = getpid();
-        r = kcmp(pid, pid, KCMP_FILE, a, b);
-        if (r == 0)
-                return true;
-        if (r > 0)
-                return false;
-        if (errno != ENOSYS)
-                return -errno;
-
-        /* We don't have kcmp(), use fstat() instead. */
-        if (fstat(a, &sta) < 0)
-                return -errno;
-
-        if (fstat(b, &stb) < 0)
-                return -errno;
-
-        if ((sta.st_mode & S_IFMT) != (stb.st_mode & S_IFMT))
-                return false;
-
-        /* We consider all device fds different, since two device fds
-         * might refer to quite different device contexts even though
-         * they share the same inode and backing dev_t. */
-
-        if (S_ISCHR(sta.st_mode) || S_ISBLK(sta.st_mode))
-                return false;
-
-        if (sta.st_dev != stb.st_dev || sta.st_ino != stb.st_ino)
-                return false;
-
-        /* The fds refer to the same inode on disk, let's also check
-         * if they have the same fd flags. This is useful to
-         * distuingish the read and write side of a pipe created with
-         * pipe(). */
-        fa = fcntl(a, F_GETFL);
-        if (fa < 0)
-                return -errno;
-
-        fb = fcntl(b, F_GETFL);
-        if (fb < 0)
-                return -errno;
-
-        return fa == fb;
-}
-
 int chattr_fd(int fd, bool b, unsigned mask) {
         unsigned old_attr, new_attr;