path-util.c revision bcb4e51a409d94ae670de96afb8483a4f7855294
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainen/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */
c1ebcdad1b4d950eb22219704dd9d64a89d0568fTimo Sirainenstatic int t_getcwd_noalloc(char **dir_r, size_t *asize_r,
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainen /* @UNSAFE */
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainen *error_r = t_strdup_printf("getcwd() failed: %m");
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainenstatic int path_normalize(const char *path, bool resolve_links,
d5cebe7f98e63d4e2822863ef2faa4971e8b3a5dTimo Sirainen /* @UNSAFE */
d5cebe7f98e63d4e2822863ef2faa4971e8b3a5dTimo Sirainen unsigned int link_count = 0;
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainen const char *p;
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainen /* relative; initialize npath with current directory */
9398c0935613ba038cf2275ff66c43b25092cfd0Timo Sirainen if (t_getcwd_noalloc(&npath, &asize, error_r) < 0)
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainen /* absolute; initialize npath with root */
7569ab8537418b7fc369265f26595b0ef9e4cb35Timo Sirainen while (*p != '\0') {
7569ab8537418b7fc369265f26595b0ef9e4cb35Timo Sirainen /* skip duplicate slashes */
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainen while (*p == '/')
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainen /* find end of path segment */
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainen for (segend = p; *segend != '\0' && *segend != '/'; segend++);
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainen break; /* '\0' */
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainen /* a reference to this segment; nothing to do */
47bb4a7615c85f212f061499f04f121d6d625387Timo Sirainen } else if (seglen == 2 && p[0] == '.' && p[1] == '.') {
47bb4a7615c85f212f061499f04f121d6d625387Timo Sirainen /* a reference to parent segment; back up to previous
3d370bb6763ac4af4a0d143ad7c93300d5ddff89Timo Sirainen /* allocate space if necessary */
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainen if ((size_t)((npath_pos - npath) + seglen + 1) >= asize) {
ce89e2964b6bc4925d2dd690417200a110d041c5Timo Sirainen asize = nearest_power(npath_offset + seglen + 2);
d89def103cc172eac305e0fb733e89f11dae40b5Timo Sirainen /* make sure npath now ends in slash */
137ea7ca34005345aa2304a940149b7f3774d727Timo Sirainen i_assert((size_t)((npath_pos - npath) + 1) < asize);
82f53ea81671bcc7b9bf24a34b04a4ba2752efd3Timo Sirainen /* copy segment to normalized path */
82f53ea81671bcc7b9bf24a34b04a4ba2752efd3Timo Sirainen i_assert((size_t)((npath_pos - npath) + seglen) < asize);
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainen /* stat path up to here (segend points to tail) */
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainen *error_r = t_strdup_printf("lstat() failed: %m");
e9503210d3521a6833ed62dc332fc42ffb0e7a13Timo Sirainen /* symlink */
e9503210d3521a6833ed62dc332fc42ffb0e7a13Timo Sirainen size_t lsize = 128, tlen = strlen(segend), espace;
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainen /* limit link dereferences */
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainen /* allocate space for preserving tail of previous symlink and
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainen first attempt at reading symlink with room for the tail
70c181da837ed85fc5b0426c010b65609bda5329Timo Sirainen buffer will look like this:
d5cebe7f98e63d4e2822863ef2faa4971e8b3a5dTimo Sirainen [npath][0][preserved tail][link buffer][room for tail][0]
8eb94c5190ba09bb6f6f068eec7bf96750f08d1dTimo Sirainen if ((size_t)((npath_pos - npath) + espace + lsize) >= asize) {
e9503210d3521a6833ed62dc332fc42ffb0e7a13Timo Sirainen asize = nearest_power((npath_offset + espace + lsize) + 1);
25ee72451d16374ed27fdbf829f4ec756c778352Timo Sirainen /* preserve tail just after end of npath */
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainen i_assert((size_t)((npath_pos + 1 - npath) + ltlen) < asize);
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainen /* read the symlink after the preserved tail */
47bb4a7615c85f212f061499f04f121d6d625387Timo Sirainen i_assert((size_t)((npath_link - npath) + lsize) < asize);
47bb4a7615c85f212f061499f04f121d6d625387Timo Sirainen /* attempt to read the link */
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainen if ((ret=readlink(npath, npath_link, lsize)) < 0) {
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainen *error_r = t_strdup_printf("readlink() failed: %m");
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainen /* POSIX doesn't guarantee the presence of a NIL */
137ea7ca34005345aa2304a940149b7f3774d727Timo Sirainen /* sum of new symlink content length
137ea7ca34005345aa2304a940149b7f3774d727Timo Sirainen * and path tail length may not
137ea7ca34005345aa2304a940149b7f3774d727Timo Sirainen exceed maximum */
137ea7ca34005345aa2304a940149b7f3774d727Timo Sirainen if ((size_t)(ret + tlen) >= PATH_UTIL_MAX_PATH) {
82f53ea81671bcc7b9bf24a34b04a4ba2752efd3Timo Sirainen /* try again with bigger buffer,
d5cebe7f98e63d4e2822863ef2faa4971e8b3a5dTimo Sirainen we need to allocate more space as well if lsize == ret,
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainen because the returned link may have gotten truncated */
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainen if ((size_t)((npath_pos - npath) + espace + lsize) >= asize ||
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainen asize = nearest_power((npath_offset + espace + lsize) + 1);
e9503210d3521a6833ed62dc332fc42ffb0e7a13Timo Sirainen /* add tail of previous path at end of symlink */
e9503210d3521a6833ed62dc332fc42ffb0e7a13Timo Sirainen i_assert((size_t)((npath_pos - npath) + 1 + tlen) < asize);
e9503210d3521a6833ed62dc332fc42ffb0e7a13Timo Sirainen i_assert((size_t)((npath_link - npath) + ret + tlen) < asize);
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainen memcpy(npath_link + ret, npath_pos + 1, tlen);
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainen i_assert((size_t)((npath_link - npath) + ret + tlen) < asize);
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainen /* use as new source path */
70c181da837ed85fc5b0426c010b65609bda5329Timo Sirainen /* absolute symlink; start over at root */
70c181da837ed85fc5b0426c010b65609bda5329Timo Sirainen /* relative symlink; back up to previous segment */
e9503210d3521a6833ed62dc332fc42ffb0e7a13Timo Sirainen } else if (*segend != '\0' && !S_ISDIR (st.st_mode)) {
e9503210d3521a6833ed62dc332fc42ffb0e7a13Timo Sirainen /* not last segment, but not a directory either */
a3dd97fb6d92a89c3de0597fed2d4b044c7aeb84Timo Sirainen *error_r = t_strdup_printf("Not a directory: %s", npath);
35136dd2baf8dc30e4e754294ed81ff48e8c1e64Timo Sirainen i_assert((size_t)(npath_pos - npath) < asize);
35136dd2baf8dc30e4e754294ed81ff48e8c1e64Timo Sirainen /* remove any trailing slash */
35136dd2baf8dc30e4e754294ed81ff48e8c1e64Timo Sirainen if ((npath_pos - npath) > 1 && *(npath_pos-1) == '/')
35136dd2baf8dc30e4e754294ed81ff48e8c1e64Timo Sirainenint t_normpath(const char *path, const char **npath_r, const char **error_r)
35136dd2baf8dc30e4e754294ed81ff48e8c1e64Timo Sirainen return path_normalize(path, FALSE, npath_r, error_r);
35136dd2baf8dc30e4e754294ed81ff48e8c1e64Timo Sirainenint t_normpath_to(const char *path, const char *root, const char **npath_r,
35136dd2baf8dc30e4e754294ed81ff48e8c1e64Timo Sirainen const char **error_r)
35136dd2baf8dc30e4e754294ed81ff48e8c1e64Timo Sirainen return t_normpath(t_strconcat(root, "/", path, NULL), npath_r, error_r);
35136dd2baf8dc30e4e754294ed81ff48e8c1e64Timo Sirainenint t_realpath(const char *path, const char **npath_r, const char **error_r)
35136dd2baf8dc30e4e754294ed81ff48e8c1e64Timo Sirainen return path_normalize(path, TRUE, npath_r, error_r);
35136dd2baf8dc30e4e754294ed81ff48e8c1e64Timo Sirainenint t_realpath_to(const char *path, const char *root, const char **npath_r,
35136dd2baf8dc30e4e754294ed81ff48e8c1e64Timo Sirainen const char **error_r)
35136dd2baf8dc30e4e754294ed81ff48e8c1e64Timo Sirainen return t_realpath(t_strconcat(root, "/", path, NULL), npath_r, error_r);
35136dd2baf8dc30e4e754294ed81ff48e8c1e64Timo Sirainenint t_abspath(const char *path, const char **abspath_r, const char **error_r)
137ea7ca34005345aa2304a940149b7f3774d727Timo Sirainen *error_r = t_strconcat("Failed to get working directory: ",
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainen *abspath_r = t_strconcat(dir, "/", path, NULL);
0f39a57760d93cddbce3ca43096d78e0fe2f42fdTimo Sirainenconst char *t_abspath_to(const char *path, const char *root)
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainenint t_get_working_dir(const char **dir_r, const char **error_r)
d5cebe7f98e63d4e2822863ef2faa4971e8b3a5dTimo Sirainen if (t_getcwd_noalloc(&dir, NULL, error_r) < 0)
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainenint t_readlink(const char *path, const char **dest_r, const char **error_r)
e9503210d3521a6833ed62dc332fc42ffb0e7a13Timo Sirainen /* @UNSAFE */
70c181da837ed85fc5b0426c010b65609bda5329Timo Sirainen while ((ret = readlink(path, dest, size)) >= (ssize_t)size) {
bd354c19cb93c07ade79477674328a54146ea332Timo Sirainen *error_r = t_strdup_printf("readlink() failed: %m");
e9503210d3521a6833ed62dc332fc42ffb0e7a13Timo Sirainenbool t_binary_abspath(const char **binpath, const char **error_r)
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainen /* already have absolute path */
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainen /* relative to current directory */
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainen if (t_abspath(*binpath, binpath, &error) < 0) {
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainen *error_r = t_strdup_printf("t_abspath(%s) failed: %s",
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainen } else if ((path_env = getenv("PATH")) != NULL) {
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainen /* we have to find our executable from path */
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainen *error_r = "Could not find the wanted executable from PATH";
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainen *error_r = "PATH environment variable undefined";