chiark / gitweb /
tree-wide: remove Lennart's copyright lines
[elogind.git] / src / basic / path-util.c
index 9dd07333889fdc588811248b4bd8b26069f31cbe..45713d6d76fed8a6c32a216502054af0a1c40174 100644 (file)
@@ -1,21 +1,4 @@
-/***
-  This file is part of systemd.
-
-  Copyright 2010-2012 Lennart Poettering
-
-  systemd is free software; you can redistribute it and/or modify it
-  under the terms of the GNU Lesser General Public License as published by
-  the Free Software Foundation; either version 2.1 of the License, or
-  (at your option) any later version.
-
-  systemd is distributed in the hope that it will be useful, but
-  WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-  Lesser General Public License for more details.
-
-  You should have received a copy of the GNU Lesser General Public License
-  along with systemd; If not, see <http://www.gnu.org/licenses/>.
-***/
+/* SPDX-License-Identifier: LGPL-2.1+ */
 
 #include <errno.h>
 #include <limits.h>
 #include "alloc-util.h"
 #include "extract-word.h"
 #include "fs-util.h"
+//#include "glob-util.h"
 #include "log.h"
 #include "macro.h"
 #include "missing.h"
+#include "parse-util.h"
 #include "path-util.h"
 #include "stat-util.h"
 #include "string-util.h"
 #include "strv.h"
 #include "time-util.h"
+//#include "utf8.h"
 
 bool path_is_absolute(const char *p) {
         return p[0] == '/';
@@ -79,15 +65,37 @@ char *path_make_absolute(const char *p, const char *prefix) {
         /* Makes every item in the list an absolute path by prepending
          * the prefix, if specified and necessary */
 
-        if (path_is_absolute(p) || !prefix)
+        if (path_is_absolute(p) || isempty(prefix))
                 return strdup(p);
 
-        return strjoin(prefix, "/", p);
+        if (endswith(prefix, "/"))
+                return strjoin(prefix, p);
+        else
+                return strjoin(prefix, "/", p);
 }
 #endif // 0
 
+int safe_getcwd(char **ret) {
+        char *cwd;
+
+        cwd = get_current_dir_name();
+        if (!cwd)
+                return negative_errno();
+
+        /* Let's make sure the directory is really absolute, to protect us from the logic behind
+         * CVE-2018-1000001 */
+        if (cwd[0] != '/') {
+                free(cwd);
+                return -ENOMEDIUM;
+        }
+
+        *ret = cwd;
+        return 0;
+}
+
 int path_make_absolute_cwd(const char *p, char **ret) {
         char *c;
+        int r;
 
         assert(p);
         assert(ret);
@@ -100,11 +108,14 @@ int path_make_absolute_cwd(const char *p, char **ret) {
         else {
                 _cleanup_free_ char *cwd = NULL;
 
-                cwd = get_current_dir_name();
-                if (!cwd)
-                        return negative_errno();
+                r = safe_getcwd(&cwd);
+                if (r < 0)
+                        return r;
 
-                c = strjoin(cwd, "/", p);
+                if (endswith(cwd, "/"))
+                        c = strjoin(cwd, p);
+                else
+                        c = strjoin(cwd, "/", p);
         }
         if (!c)
                 return -ENOMEM;
@@ -115,8 +126,8 @@ int path_make_absolute_cwd(const char *p, char **ret) {
 
 #if 0 /// UNNEEDED by elogind
 int path_make_relative(const char *from_dir, const char *to_path, char **_r) {
-        char *r, *p;
-        unsigned n_parents;
+        char *f, *t, *r, *p;
+        unsigned n_parents = 0;
 
         assert(from_dir);
         assert(to_path);
@@ -124,76 +135,81 @@ int path_make_relative(const char *from_dir, const char *to_path, char **_r) {
 
         /* Strips the common part, and adds ".." elements as necessary. */
 
-        if (!path_is_absolute(from_dir))
+        if (!path_is_absolute(from_dir) || !path_is_absolute(to_path))
                 return -EINVAL;
 
-        if (!path_is_absolute(to_path))
-                return -EINVAL;
+        f = strdupa(from_dir);
+        t = strdupa(to_path);
+
+        path_simplify(f, true);
+        path_simplify(t, true);
 
         /* Skip the common part. */
         for (;;) {
-                size_t a;
-                size_t b;
+                size_t a, b;
 
-                from_dir += strspn(from_dir, "/");
-                to_path += strspn(to_path, "/");
+                f += *f == '/';
+                t += *t == '/';
 
-                if (!*from_dir) {
-                        if (!*to_path)
+                if (!*f) {
+                        if (!*t)
                                 /* from_dir equals to_path. */
                                 r = strdup(".");
                         else
                                 /* from_dir is a parent directory of to_path. */
-                                r = strdup(to_path);
-
+                                r = strdup(t);
                         if (!r)
                                 return -ENOMEM;
 
-                        path_kill_slashes(r);
-
                         *_r = r;
                         return 0;
                 }
 
-                if (!*to_path)
+                if (!*t)
                         break;
 
-                a = strcspn(from_dir, "/");
-                b = strcspn(to_path, "/");
+                a = strcspn(f, "/");
+                b = strcspn(t, "/");
 
-                if (a != b)
-                        break;
-
-                if (memcmp(from_dir, to_path, a) != 0)
+                if (a != b || memcmp(f, t, a) != 0)
                         break;
 
-                from_dir += a;
-                to_path += b;
+                f += a;
+                t += b;
         }
 
         /* If we're here, then "from_dir" has one or more elements that need to
          * be replaced with "..". */
 
         /* Count the number of necessary ".." elements. */
-        for (n_parents = 0;;) {
-                from_dir += strspn(from_dir, "/");
+        for (; *f;) {
+                size_t w;
 
-                if (!*from_dir)
-                        break;
+                w = strcspn(f, "/");
+
+                /* If this includes ".." we can't do a simple series of "..", refuse */
+                if (w == 2 && f[0] == '.' && f[1] == '.')
+                        return -EINVAL;
 
-                from_dir += strcspn(from_dir, "/");
+                /* Count number of elements */
                 n_parents++;
+
+                f += w;
+                f += *f == '/';
         }
 
-        r = malloc(n_parents * 3 + strlen(to_path) + 1);
+        r = new(char, n_parents * 3 + strlen(t) + 1);
         if (!r)
                 return -ENOMEM;
 
-        for (p = r; n_parents > 0; n_parents--, p += 3)
-                memcpy(p, "../", 3);
+        for (p = r; n_parents > 0; n_parents--)
+                p = mempcpy(p, "../", 3);
 
-        strcpy(p, to_path);
-        path_kill_slashes(r);
+        if (*t)
+                strcpy(p, t);
+        else
+                /* Remove trailing slash */
+                *(--p) = 0;
 
         *_r = r;
         return 0;
@@ -214,8 +230,8 @@ int path_strv_make_absolute_cwd(char **l) {
                 if (r < 0)
                         return r;
 
-                free(*s);
-                *s = t;
+                path_simplify(t, false);
+                free_and_replace(*s, t);
         }
 
         return 0;
@@ -254,11 +270,10 @@ char **path_strv_resolve(char **l, const char *root) {
                 } else
                         t = *s;
 
-                r = chase_symlinks(t, root, &u);
+                r = chase_symlinks(t, root, 0, &u);
                 if (r == -ENOENT) {
                         if (root) {
-                                u = orig;
-                                orig = NULL;
+                                u = TAKE_PTR(orig);
                                 free(t);
                         } else
                                 u = t;
@@ -316,17 +331,30 @@ char **path_strv_resolve_uniq(char **l, const char *root) {
         return strv_uniq(l);
 }
 
-char *path_kill_slashes(char *path) {
+char *path_simplify(char *path, bool kill_dots) {
         char *f, *t;
-        bool slash = false;
+        bool slash = false, ignore_slash = false, absolute;
+
+        assert(path);
 
-        /* Removes redundant inner and trailing slashes. Modifies the
-         * passed string in-place.
+        /* Removes redundant inner and trailing slashes. Also removes unnecessary dots
+         * if kill_dots is true. Modifies the passed string in-place.
          *
-         * ///foo///bar/ becomes /foo/bar
+         * ///foo//./bar/.   becomes /foo/./bar/.  (if kill_dots is false)
+         * ///foo//./bar/.   becomes /foo/bar      (if kill_dots is true)
+         * .//./foo//./bar/. becomes ./foo/bar     (if kill_dots is false)
+         * .//./foo//./bar/. becomes foo/bar       (if kill_dots is true)
          */
 
-        for (f = path, t = path; *f; f++) {
+        absolute = path_is_absolute(path);
+
+        f = path;
+        if (kill_dots && *f == '.' && IN_SET(f[1], 0, '/')) {
+                ignore_slash = true;
+                f++;
+        }
+
+        for (t = path; *f; f++) {
 
                 if (*f == '/') {
                         slash = true;
@@ -334,17 +362,21 @@ char *path_kill_slashes(char *path) {
                 }
 
                 if (slash) {
+                        if (kill_dots && *f == '.' && IN_SET(f[1], 0, '/'))
+                                continue;
+
                         slash = false;
-                        *(t++) = '/';
+                        if (ignore_slash)
+                                ignore_slash = false;
+                        else
+                                *(t++) = '/';
                 }
 
                 *(t++) = *f;
         }
 
-        /* Special rule, if we are talking of the root directory, a
-        trailing slash is good */
-
-        if (t == path && slash)
+        /* Special rule, if we are talking of the root directory, a trailing slash is good */
+        if (absolute && t == path)
                 *(t++) = '/';
 
         *t = 0;
@@ -444,11 +476,10 @@ bool path_equal(const char *a, const char *b) {
         return path_compare(a, b) == 0;
 }
 
-bool path_equal_or_files_same(const char *a, const char *b) {
-        return path_equal(a, b) || files_same(a, b) > 0;
+bool path_equal_or_files_same(const char *a, const char *b, int flags) {
+        return path_equal(a, b) || files_same(a, b, flags) > 0;
 }
 
-#if 0 /// UNNEEDED by elogind
 char* path_join(const char *root, const char *path, const char *rest) {
         assert(path);
 
@@ -512,7 +543,7 @@ int find_binary(const char *name, char **ret) {
                         /* Found it! */
 
                         if (ret) {
-                                *ret = path_kill_slashes(j);
+                                *ret = path_simplify(j, false);
                                 j = NULL;
                         }
 
@@ -525,13 +556,14 @@ int find_binary(const char *name, char **ret) {
         return last_error;
 }
 
+#if 0 /// UNNEEDED by elogind
 bool paths_check_timestamp(const char* const* paths, usec_t *timestamp, bool update) {
         bool changed = false;
         const char* const* i;
 
         assert(timestamp);
 
-        if (paths == NULL)
+        if (!paths)
                 return false;
 
         STRV_FOREACH(i, paths) {
@@ -608,6 +640,7 @@ int mkfs_exists(const char *fstype) {
         mkfs = strjoina("mkfs.", fstype);
         return binary_is_good(mkfs);
 }
+#endif // 0
 
 char *prefix_root(const char *root, const char *path) {
         char *n, *p;
@@ -622,7 +655,7 @@ char *prefix_root(const char *root, const char *path) {
         while (path[0] == '/' && path[1] == '/')
                 path++;
 
-        if (isempty(root) || path_equal(root, "/"))
+        if (empty_or_root(root))
                 return strdup(path);
 
         l = strlen(root) + 1 + strlen(path) + 1;
@@ -643,6 +676,7 @@ char *prefix_root(const char *root, const char *path) {
         return n;
 }
 
+#if 0 /// UNNEEDED by elogind
 int parse_path_argument_and_warn(const char *path, bool suppress_root, char **arg) {
         char *p;
         int r;
@@ -666,12 +700,12 @@ int parse_path_argument_and_warn(const char *path, bool suppress_root, char **ar
         if (r < 0)
                 return log_error_errno(r, "Failed to parse path \"%s\" and make it absolute: %m", path);
 
-        path_kill_slashes(p);
-        if (suppress_root && path_equal(p, "/"))
+        path_simplify(p, false);
+        if (suppress_root && empty_or_root(p))
                 p = mfree(p);
 
-        free(*arg);
-        *arg = p;
+        free_and_replace(*arg, p);
+
         return 0;
 }
 #endif // 0
@@ -697,16 +731,51 @@ char* dirname_malloc(const char *path) {
         return dir2;
 }
 
+const char *last_path_component(const char *path) {
+
+        /* Finds the last component of the path, preserving the optional trailing slash that signifies a directory.
+         *
+         *    a/b/c → c
+         *    a/b/c/ → c/
+         *    x → x
+         *    x/ → x/
+         *    /y → y
+         *    /y/ → y/
+         *    / → /
+         *    // → /
+         *    /foo/a → a
+         *    /foo/a/ → a/
+         *
+         *    Also, the empty string is mapped to itself.
+         *
+         * This is different than basename(), which returns "" when a trailing slash is present.
+         */
+
+        unsigned l, k;
+
+        l = k = strlen(path);
+        if (l == 0) /* special case — an empty string */
+                return path;
+
+        while (k > 0 && path[k-1] == '/')
+                k--;
+
+        if (k == 0) /* the root directory */
+                return path + l - 1;
+
+        while (k > 0 && path[k-1] != '/')
+                k--;
+
+        return path + k;
+}
+
 bool filename_is_valid(const char *p) {
         const char *e;
 
         if (isempty(p))
                 return false;
 
-        if (streq(p, "."))
-                return false;
-
-        if (streq(p, ".."))
+        if (dot_or_dot_dot(p))
                 return false;
 
         e = strchrnul(p, '/');
@@ -719,19 +788,21 @@ bool filename_is_valid(const char *p) {
         return true;
 }
 
-bool path_is_safe(const char *p) {
+bool path_is_normalized(const char *p) {
 
         if (isempty(p))
                 return false;
 
-        if (streq(p, "..") || startswith(p, "../") || endswith(p, "/..") || strstr(p, "/../"))
+        if (dot_or_dot_dot(p))
+                return false;
+
+        if (startswith(p, "../") || endswith(p, "/..") || strstr(p, "/../"))
                 return false;
 
         if (strlen(p)+1 > PATH_MAX)
                 return false;
 
-        /* The following two checks are not really dangerous, but hey, they still are confusing */
-        if (streq(p, ".") || startswith(p, "./") || endswith(p, "/.") || strstr(p, "/./"))
+        if (startswith(p, "./") || endswith(p, "/.") || strstr(p, "/./"))
                 return false;
 
         if (strstr(p, "//"))
@@ -819,17 +890,36 @@ bool hidden_or_backup_file(const char *filename) {
 #if 0 /// UNNEEDED by elogind
 bool is_device_path(const char *path) {
 
-        /* Returns true on paths that refer to a device, either in
-         * sysfs or in /dev */
+        /* Returns true on paths that likely refer to a device, either by path in sysfs or to something in /dev */
+
+        return PATH_STARTSWITH_SET(path, "/dev/", "/sys/");
+}
+
+bool valid_device_node_path(const char *path) {
+
+        /* Some superficial checks whether the specified path is a valid device node path, all without looking at the
+         * actual device node. */
+
+        if (!PATH_STARTSWITH_SET(path, "/dev/", "/run/systemd/inaccessible/"))
+                return false;
+
+        if (endswith(path, "/")) /* can't be a device node if it ends in a slash */
+                return false;
 
-        return path_startswith(path, "/dev/") ||
-               path_startswith(path, "/sys/");
+        return path_is_normalized(path);
 }
 
-bool is_deviceallow_pattern(const char *path) {
-        return path_startswith(path, "/dev/") ||
-               startswith(path, "block-") ||
-               startswith(path, "char-");
+bool valid_device_allow_pattern(const char *path) {
+        assert(path);
+
+        /* Like valid_device_node_path(), but also allows full-subsystem expressions, like DeviceAllow= and DeviceDeny=
+         * accept it */
+
+        if (startswith(path, "block-") ||
+            startswith(path, "char-"))
+                return true;
+
+        return valid_device_node_path(path);
 }
 
 int systemd_installation_has_version(const char *root, unsigned minimal_version) {
@@ -847,7 +937,9 @@ int systemd_installation_has_version(const char *root, unsigned minimal_version)
                         * for Gentoo which does a merge without making /lib a symlink.
                         */
                        "lib/systemd/libsystemd-shared-*.so\0"
-                       "usr/lib/systemd/libsystemd-shared-*.so\0") {
+                       "lib64/systemd/libsystemd-shared-*.so\0"
+                       "usr/lib/systemd/libsystemd-shared-*.so\0"
+                       "usr/lib64/systemd/libsystemd-shared-*.so\0") {
 
                 _cleanup_strv_free_ char **names = NULL;
                 _cleanup_free_ char *path = NULL;
@@ -863,7 +955,7 @@ int systemd_installation_has_version(const char *root, unsigned minimal_version)
                 if (r < 0)
                         return r;
 
-                assert_se((c = endswith(path, "*.so")));
+                assert_se(c = endswith(path, "*.so"));
                 *c = '\0'; /* truncate the glob part */
 
                 STRV_FOREACH(name, names) {
@@ -898,3 +990,74 @@ int systemd_installation_has_version(const char *root, unsigned minimal_version)
         return false;
 }
 #endif // 0
+
+bool dot_or_dot_dot(const char *path) {
+        if (!path)
+                return false;
+        if (path[0] != '.')
+                return false;
+        if (path[1] == 0)
+                return true;
+        if (path[1] != '.')
+                return false;
+
+        return path[2] == 0;
+}
+
+bool empty_or_root(const char *root) {
+
+        /* For operations relative to some root directory, returns true if the specified root directory is redundant,
+         * i.e. either / or NULL or the empty string or any equivalent. */
+
+        if (!root)
+                return true;
+
+        return root[strspn(root, "/")] == 0;
+}
+
+int path_simplify_and_warn(
+                char *path,
+                unsigned flag,
+                const char *unit,
+                const char *filename,
+                unsigned line,
+                const char *lvalue) {
+
+        bool absolute, fatal = flag & PATH_CHECK_FATAL;
+
+        assert(!FLAGS_SET(flag, PATH_CHECK_ABSOLUTE | PATH_CHECK_RELATIVE));
+
+        if (!utf8_is_valid(path)) {
+                log_syntax_invalid_utf8(unit, LOG_ERR, filename, line, path);
+                return -EINVAL;
+        }
+
+        if (flag & (PATH_CHECK_ABSOLUTE | PATH_CHECK_RELATIVE)) {
+                absolute = path_is_absolute(path);
+
+                if (!absolute && (flag & PATH_CHECK_ABSOLUTE)) {
+                        log_syntax(unit, LOG_ERR, filename, line, 0,
+                                   "%s= path is not absolute%s: %s",
+                                   lvalue, fatal ? "" : ", ignoring", path);
+                        return -EINVAL;
+                }
+
+                if (absolute && (flag & PATH_CHECK_RELATIVE)) {
+                        log_syntax(unit, LOG_ERR, filename, line, 0,
+                                   "%s= path is absolute%s: %s",
+                                   lvalue, fatal ? "" : ", ignoring", path);
+                        return -EINVAL;
+                }
+        }
+
+        path_simplify(path, true);
+
+        if (!path_is_normalized(path)) {
+                log_syntax(unit, LOG_ERR, filename, line, 0,
+                           "%s= path is not normalized%s: %s",
+                           lvalue, fatal ? "" : ", ignoring", path);
+                return -EINVAL;
+        }
+
+        return 0;
+}