|
11 | 11 | #include <errno.h> |
12 | 12 | #include <endian.h> |
13 | 13 | #include <sys/stat.h> |
| 14 | +#include <fcntl.h> |
| 15 | +#include <stdlib.h> |
| 16 | +#include <limits.h> |
14 | 17 |
|
15 | 18 | static inline void lcfs_node_unrefp(struct lcfs_node_s **nodep) |
16 | 19 | { |
@@ -227,6 +230,82 @@ static void test_hardlinked_whiteout_load(void) |
227 | 230 | assert(errsv == EINVAL); |
228 | 231 | } |
229 | 232 |
|
| 233 | +// Verifies that lcfs_build() only detects hardlinked regular files when |
| 234 | +// scanning a directory tree if LCFS_BUILD_TRACK_HARDLINKS is passed; by |
| 235 | +// default (matching historical behavior) every path gets its own |
| 236 | +// independent inode with nlink == 1. |
| 237 | +static void test_build_hardlinks(void) |
| 238 | +{ |
| 239 | + char tmpdir[] = "/tmp/test-build-hardlinks.XXXXXX"; |
| 240 | + assert(mkdtemp(tmpdir) != NULL); |
| 241 | + |
| 242 | + char path1[PATH_MAX], path2[PATH_MAX]; |
| 243 | + snprintf(path1, sizeof(path1), "%s/file1", tmpdir); |
| 244 | + snprintf(path2, sizeof(path2), "%s/file2", tmpdir); |
| 245 | + |
| 246 | + int fd = open(path1, O_WRONLY | O_CREAT | O_TRUNC, 0644); |
| 247 | + assert(fd >= 0); |
| 248 | + assert(write(fd, "hello", 5) == 5); |
| 249 | + close(fd); |
| 250 | + |
| 251 | + assert(link(path1, path2) == 0); |
| 252 | + |
| 253 | + int dirfd = open(tmpdir, O_RDONLY | O_DIRECTORY); |
| 254 | + assert(dirfd >= 0); |
| 255 | + |
| 256 | + /* Without LCFS_BUILD_TRACK_HARDLINKS: today's default behavior is |
| 257 | + * that every path becomes an independent inode with nlink == 1. */ |
| 258 | + { |
| 259 | + char *failed_path = NULL; |
| 260 | + cleanup_node struct lcfs_node_s *root = |
| 261 | + lcfs_build(dirfd, ".", 0, &failed_path); |
| 262 | + assert(root != NULL); |
| 263 | + |
| 264 | + struct lcfs_node_s *n1 = lcfs_node_lookup_child(root, "file1"); |
| 265 | + struct lcfs_node_s *n2 = lcfs_node_lookup_child(root, "file2"); |
| 266 | + assert(n1 != NULL); |
| 267 | + assert(n2 != NULL); |
| 268 | + assert(lcfs_node_get_hardlink_target(n1) == NULL); |
| 269 | + assert(lcfs_node_get_hardlink_target(n2) == NULL); |
| 270 | + assert(lcfs_node_get_nlink(n1) == 1); |
| 271 | + assert(lcfs_node_get_nlink(n2) == 1); |
| 272 | + |
| 273 | + assert(testwrite_node(root) == 0); |
| 274 | + } |
| 275 | + |
| 276 | + /* With LCFS_BUILD_TRACK_HARDLINKS: one of the two paths becomes a |
| 277 | + * hardlink to the other, and the target's nlink is bumped to 2. */ |
| 278 | + { |
| 279 | + char *failed_path = NULL; |
| 280 | + cleanup_node struct lcfs_node_s *root = lcfs_build( |
| 281 | + dirfd, ".", LCFS_BUILD_TRACK_HARDLINKS, &failed_path); |
| 282 | + assert(root != NULL); |
| 283 | + |
| 284 | + struct lcfs_node_s *n1 = lcfs_node_lookup_child(root, "file1"); |
| 285 | + struct lcfs_node_s *n2 = lcfs_node_lookup_child(root, "file2"); |
| 286 | + assert(n1 != NULL); |
| 287 | + assert(n2 != NULL); |
| 288 | + |
| 289 | + /* Whichever of the two is visited first by readdir() becomes |
| 290 | + * the canonical inode; the other becomes a hardlink to it. */ |
| 291 | + struct lcfs_node_s *target1 = lcfs_node_get_hardlink_target(n1); |
| 292 | + struct lcfs_node_s *target2 = lcfs_node_get_hardlink_target(n2); |
| 293 | + assert((target1 == NULL) != (target2 == NULL)); |
| 294 | + |
| 295 | + struct lcfs_node_s *link_node = target1 != NULL ? n1 : n2; |
| 296 | + struct lcfs_node_s *target_node = target1 != NULL ? n2 : n1; |
| 297 | + assert(lcfs_node_get_hardlink_target(link_node) == target_node); |
| 298 | + assert(lcfs_node_get_nlink(target_node) == 2); |
| 299 | + |
| 300 | + assert(testwrite_node(root) == 0); |
| 301 | + } |
| 302 | + |
| 303 | + close(dirfd); |
| 304 | + unlink(path1); |
| 305 | + unlink(path2); |
| 306 | + rmdir(tmpdir); |
| 307 | +} |
| 308 | + |
230 | 309 | // Verifies that lcfs_fd_measure_fsverity fails on a fd without fsverity |
231 | 310 | static void test_no_verity(void) |
232 | 311 | { |
@@ -255,4 +334,5 @@ int main(int argc, char **argv) |
255 | 334 | test_xattr_addremove(); |
256 | 335 | test_xattr_doubleadd(); |
257 | 336 | test_hardlinked_whiteout_load(); |
| 337 | + test_build_hardlinks(); |
258 | 338 | } |
0 commit comments