stat-util: fix null_or_empty_path_with_root() below a root
null_or_empty_path_with_root() passes a path that already includes the root to chase_and_stat() with CHASE_PREFIX_ROOT, so the root is added twice. It also only recognizes /dev/null when the path itself is /dev/null. Below a root, a symlink to an empty file therefore fails with ENOENT. A symlink to /dev/null fails the same way when the image has no /dev/null. Resolve the symlink inside the root without requiring the target to exist, and stat the target through the O_PATH fd that chase() returns. chase() accepts ret_fd together with CHASE_NONEXISTENT and sets the fd to -EBADF when the target is missing. Its comment says that ret_fd cannot be combined with CHASE_NONEXISTENT. Fix the comment. (cherry picked from commit f2b0c29597466bdcc800dcfa8da4c8192fb9e824)
This commit is contained in:
committed by
Luca Boccassi
parent
5f73eab263
commit
b7ae93f093
+2
-2
@@ -286,8 +286,8 @@ int chaseat(int root_fd, int dir_fd, const char *path, ChaseFlags flags, char **
|
||||
* 2. With ret_fd: in this case the destination is opened after chasing it as O_PATH and this file
|
||||
* descriptor is returned as return value. This is useful to open files relative to some root
|
||||
* directory. Note that the returned O_PATH file descriptors must be converted into a regular one
|
||||
* (using fd_reopen() or such) before it can be used for reading/writing. ret_fd may not be
|
||||
* combined with CHASE_NONEXISTENT.
|
||||
* (using fd_reopen() or such) before it can be used for reading/writing. If CHASE_NONEXISTENT
|
||||
* is also set and the destination doesn't exist, 0 is returned and ret_fd is set to -EBADF.
|
||||
*
|
||||
* 3. With CHASE_STEP: in this case only a single step of the normalization is executed, i.e. only
|
||||
* the first symlink or ".." component of the path is resolved, and the resulting path is
|
||||
|
||||
+22
-4
@@ -342,22 +342,40 @@ bool stat_is_empty(struct stat *st) {
|
||||
}
|
||||
|
||||
int null_or_empty_path_with_root(const char *fn, const char *root) {
|
||||
_cleanup_free_ char *resolved = NULL;
|
||||
_cleanup_close_ int fd = -EBADF;
|
||||
struct stat st;
|
||||
int r;
|
||||
|
||||
assert(fn);
|
||||
|
||||
/* A symlink to /dev/null or an empty file?
|
||||
* When looking under root_dir, we can't expect /dev/ to be mounted,
|
||||
* so let's see if the path is a (possibly dangling) symlink to /dev/null. */
|
||||
/* fn includes the root prefix. */
|
||||
|
||||
if (path_equal(path_startswith(fn, root ?: "/"), "dev/null"))
|
||||
return true;
|
||||
|
||||
r = chase_and_stat(fn, root, CHASE_PREFIX_ROOT, NULL, &st);
|
||||
if (empty_or_root(root)) {
|
||||
if (stat(fn, &st) < 0)
|
||||
return -errno;
|
||||
|
||||
return null_or_empty(&st);
|
||||
}
|
||||
|
||||
/* An image below a root directory usually has no /dev/null, so a symlink to /dev/null dangles there.
|
||||
* CHASE_NONEXISTENT resolves such a symlink without requiring the target to exist. */
|
||||
r = chase(fn, root, CHASE_NONEXISTENT, &resolved, &fd);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (path_equal(path_startswith(resolved, root), "dev/null"))
|
||||
return true;
|
||||
|
||||
if (r == 0)
|
||||
return -ENOENT;
|
||||
|
||||
if (fstat(fd, &st) < 0)
|
||||
return -errno;
|
||||
|
||||
return null_or_empty(&st);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "errno-list.h"
|
||||
#include "errno-util.h"
|
||||
#include "fd-util.h"
|
||||
#include "fileio.h"
|
||||
#include "fs-util.h"
|
||||
#include "mount-util.h"
|
||||
#include "mountpoint-util.h"
|
||||
@@ -78,6 +79,29 @@ TEST(null_or_empty_path_with_root) {
|
||||
assert_se(null_or_empty_path_with_root("/foobar/barbar/dev/null", "/foobar/barbar/") == 1);
|
||||
}
|
||||
|
||||
TEST(null_or_empty_path_with_root_symlinks) {
|
||||
_cleanup_(rm_rf_physical_and_freep) char *root = NULL;
|
||||
|
||||
ASSERT_OK(mkdtemp_malloc("/tmp/test-stat-util-XXXXXX", &root));
|
||||
ASSERT_OK_ERRNO(mkdir(strjoina(root, "/etc"), 0755));
|
||||
|
||||
/* The root has no /dev/null, so the symlink to it dangles. */
|
||||
ASSERT_OK_ERRNO(symlink("/dev/null", strjoina(root, "/etc/null")));
|
||||
ASSERT_OK_ERRNO(symlink("../../../../../../dev/null", strjoina(root, "/etc/null-relative")));
|
||||
ASSERT_OK(touch(strjoina(root, "/etc/empty")));
|
||||
ASSERT_OK_ERRNO(symlink("/etc/empty", strjoina(root, "/etc/to-empty")));
|
||||
ASSERT_OK(write_string_file(strjoina(root, "/etc/full"), "full", WRITE_STRING_FILE_CREATE));
|
||||
ASSERT_OK_ERRNO(symlink("full", strjoina(root, "/etc/to-full")));
|
||||
|
||||
ASSERT_OK_POSITIVE(null_or_empty_path_with_root(strjoina(root, "/etc/null"), root));
|
||||
ASSERT_OK_POSITIVE(null_or_empty_path_with_root(strjoina(root, "/etc/null-relative"), root));
|
||||
ASSERT_OK_POSITIVE(null_or_empty_path_with_root(strjoina(root, "/etc/empty"), root));
|
||||
ASSERT_OK_POSITIVE(null_or_empty_path_with_root(strjoina(root, "/etc/to-empty"), root));
|
||||
ASSERT_OK_ZERO(null_or_empty_path_with_root(strjoina(root, "/etc/full"), root));
|
||||
ASSERT_OK_ZERO(null_or_empty_path_with_root(strjoina(root, "/etc/to-full"), root));
|
||||
ASSERT_ERROR(null_or_empty_path_with_root(strjoina(root, "/etc/missing"), root), ENOENT);
|
||||
}
|
||||
|
||||
TEST(inode_same) {
|
||||
_cleanup_close_ int fd = -EBADF;
|
||||
_cleanup_(unlink_tempfilep) char name[] = "/tmp/test-files_same.XXXXXX";
|
||||
|
||||
Reference in New Issue
Block a user