| // Copyright 2017 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <fcntl.h> |
| #include <lib/fdio/vfs.h> |
| #include <math.h> |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <sys/stat.h> |
| #include <sys/time.h> |
| #include <unistd.h> |
| #include <zircon/syscalls.h> |
| #include <zircon/time.h> |
| #include <zircon/types.h> |
| |
| #include "filesystems.h" |
| |
| #define ROUND_DOWN(t, granularity) ((t) - ((t) % (granularity))) |
| |
| zx_time_t nstimespec(struct timespec ts) { |
| // assumes very small number of seconds in deltas |
| return zx_time_add_duration(ZX_SEC(ts.tv_sec), ts.tv_nsec); |
| } |
| |
| bool test_create_mtime(void) { |
| BEGIN_TEST; |
| zx_time_t now = 0; |
| ASSERT_EQ(ZX_OK, zx_clock_get(ZX_CLOCK_UTC, &now), "could not read clock"); |
| ASSERT_NE(now, 0u, "zx_clock_get only returns zero on error"); |
| |
| int fd1 = open("::file.txt", O_CREAT | O_RDWR, 0644); |
| ASSERT_GT(fd1, 0, ""); |
| |
| struct timespec ts[2]; |
| ts[0].tv_nsec = UTIME_OMIT; |
| ts[1].tv_sec = (long)(now / ZX_SEC(1)); |
| ts[1].tv_nsec = (long)(now % ZX_SEC(1)); |
| |
| // make sure we get back "now" from stat() |
| ASSERT_EQ(futimens(fd1, ts), 0, ""); |
| struct stat statb1; |
| ASSERT_EQ(fstat(fd1, &statb1), 0, ""); |
| now = ROUND_DOWN(now, test_info->nsec_granularity); |
| ASSERT_EQ(statb1.st_mtim.tv_sec, (long)(now / ZX_SEC(1)), ""); |
| ASSERT_EQ(statb1.st_mtim.tv_nsec, (long)(now % ZX_SEC(1)), ""); |
| ASSERT_EQ(close(fd1), 0, ""); |
| |
| ASSERT_EQ(unlink("::file.txt"), 0, ""); |
| |
| END_TEST; |
| } |
| |
| bool test_utimes_mtime(void) { |
| BEGIN_TEST; |
| zx_time_t now = 0; |
| EXPECT_EQ(ZX_OK, zx_clock_get(ZX_CLOCK_UTC, &now), "could not read clock"); |
| EXPECT_NE(now, 0u, "zx_clock_get only returns zero on error"); |
| |
| int fd1 = open("::file.txt", O_CREAT | O_RDWR, 0644); |
| EXPECT_GT(fd1, 0, ""); |
| |
| struct timespec ts[2]; |
| ts[0].tv_nsec = UTIME_OMIT; |
| ts[1].tv_sec = (long)(now / ZX_SEC(1)); |
| ts[1].tv_nsec = (long)(now % ZX_SEC(1)); |
| |
| // Make sure we get back "now" from stat(). |
| EXPECT_EQ(futimens(fd1, ts), 0, ""); |
| struct stat statb1; |
| EXPECT_EQ(fstat(fd1, &statb1), 0, ""); |
| now = ROUND_DOWN(now, test_info->nsec_granularity); |
| EXPECT_EQ(statb1.st_mtim.tv_sec, (long)(now / ZX_SEC(1)), ""); |
| EXPECT_EQ(statb1.st_mtim.tv_nsec, (long)(now % ZX_SEC(1)), ""); |
| EXPECT_EQ(close(fd1), 0, ""); |
| |
| zx_nanosleep(zx_deadline_after(test_info->nsec_granularity)); |
| |
| ASSERT_EQ(utimes("::file.txt", NULL), 0, ""); |
| struct stat statb2; |
| ASSERT_EQ(stat("::file.txt", &statb2), 0, ""); |
| ASSERT_GT(nstimespec(statb2.st_mtim), nstimespec(statb1.st_mtim), ""); |
| |
| ASSERT_EQ(unlink("::file.txt"), 0, ""); |
| |
| END_TEST; |
| } |
| |
| bool test_write_mtime(void) { |
| BEGIN_TEST; |
| |
| int fd1 = open("::file.txt", O_CREAT | O_RDWR, 0644); |
| EXPECT_GT(fd1, 0, ""); |
| |
| struct stat stat1, stat2; |
| EXPECT_EQ(fstat(fd1, &stat1), 0, ""); |
| |
| char buffer[100]; |
| memset(buffer, 'a', sizeof(buffer)); |
| ssize_t ret = write(fd1, buffer, sizeof(buffer)); |
| EXPECT_GT(ret, 0, ""); |
| EXPECT_EQ((unsigned long)(ret), sizeof(buffer), ""); |
| EXPECT_EQ(close(fd1), 0, ""); |
| ASSERT_EQ(stat("::file.txt", &stat2), 0, ""); |
| |
| ASSERT_LE(nstimespec(stat1.st_mtim), nstimespec(stat2.st_mtim), ""); |
| EXPECT_EQ(unlink("::file.txt"), 0, ""); |
| |
| END_TEST; |
| } |
| |
| bool test_blksize(void) { |
| BEGIN_TEST; |
| |
| int fd = open("::file.txt", O_CREAT | O_RDWR, 0644); |
| ASSERT_GT(fd, 0, ""); |
| |
| struct stat buf; |
| ASSERT_EQ(fstat(fd, &buf), 0, ""); |
| ASSERT_GT(buf.st_blksize, 0, "blksize should be greater than zero"); |
| ASSERT_EQ(buf.st_blksize % VNATTR_BLKSIZE, 0, "blksize should be a multiple of VNATTR_BLKSIZE"); |
| ASSERT_EQ(buf.st_blocks, 0, "Number of allocated blocks should be zero"); |
| |
| char data = {'a'}; |
| ASSERT_EQ(write(fd, &data, 1), 1, "Couldn't write a single byte to file"); |
| ASSERT_EQ(fstat(fd, &buf), 0, ""); |
| ASSERT_GT(buf.st_blksize, 0, "blksize should be greater than zero"); |
| ASSERT_EQ(buf.st_blksize % VNATTR_BLKSIZE, 0, "blksize should be a multiple of VNATTR_BLKSIZE"); |
| ASSERT_GT(buf.st_blocks, 0, "Number of allocated blocks should greater than zero"); |
| ASSERT_EQ(close(fd), 0, ""); |
| |
| blkcnt_t nblocks = buf.st_blocks; |
| ASSERT_EQ(stat("::file.txt", &buf), 0, ""); |
| ASSERT_EQ(buf.st_blocks, nblocks, "Block count changed when closing file"); |
| |
| ASSERT_EQ(unlink("::file.txt"), 0, ""); |
| |
| END_TEST; |
| } |
| |
| bool test_parent_directory_time(void) { |
| BEGIN_TEST; |
| |
| if (strcmp(test_info->name, "FAT") == 0) { |
| // FAT does not update parent directory times when children |
| // are updated |
| printf("FAT parent directory timestamps aren't updated; skipping test...\n"); |
| return true; |
| } |
| |
| zx_time_t now = 0; |
| ASSERT_EQ(ZX_OK, zx_clock_get(ZX_CLOCK_UTC, &now), "could not read clock"); |
| ASSERT_NE(now, 0u, "zx_clock_get only returns zero on error"); |
| |
| // Create a parent directory to contain new contents |
| zx_nanosleep(zx_deadline_after(test_info->nsec_granularity)); |
| ASSERT_EQ(mkdir("::parent", 0666), 0, ""); |
| ASSERT_EQ(mkdir("::parent2", 0666), 0, ""); |
| |
| // Ensure the parent directory's create + modified times |
| // were initialized correctly. |
| struct stat statb; |
| ASSERT_EQ(stat("::parent", &statb), 0, ""); |
| ASSERT_GT(nstimespec(statb.st_ctim), now, ""); |
| ASSERT_GT(nstimespec(statb.st_mtim), now, ""); |
| now = nstimespec(statb.st_ctim); |
| |
| // Create a file in the parent directory |
| zx_nanosleep(zx_deadline_after(test_info->nsec_granularity)); |
| int fd = open("::parent/child", O_CREAT | O_RDWR); |
| ASSERT_GT(fd, 0, ""); |
| ASSERT_EQ(close(fd), 0, ""); |
| |
| // Time moved forward in both the child... |
| ASSERT_EQ(stat("::parent/child", &statb), 0, ""); |
| ASSERT_GT(nstimespec(statb.st_mtim), now, ""); |
| // ... and the parent |
| ASSERT_EQ(stat("::parent", &statb), 0, ""); |
| ASSERT_GT(nstimespec(statb.st_mtim), now, ""); |
| now = nstimespec(statb.st_mtim); |
| |
| // Link the child into a second directory |
| zx_nanosleep(zx_deadline_after(test_info->nsec_granularity)); |
| ASSERT_EQ(link("::parent/child", "::parent2/child"), 0, ""); |
| // Source directory is not impacted |
| ASSERT_EQ(stat("::parent", &statb), 0, ""); |
| ASSERT_EQ(nstimespec(statb.st_mtim), now, ""); |
| // Target directory is updated |
| ASSERT_EQ(stat("::parent2", &statb), 0, ""); |
| ASSERT_GT(nstimespec(statb.st_mtim), now, ""); |
| now = nstimespec(statb.st_mtim); |
| |
| // Unlink the child, and the parent's time should |
| // move forward again |
| zx_nanosleep(zx_deadline_after(test_info->nsec_granularity)); |
| ASSERT_EQ(unlink("::parent2/child"), 0, ""); |
| ASSERT_EQ(stat("::parent2", &statb), 0, ""); |
| ASSERT_GT(nstimespec(statb.st_mtim), now, ""); |
| now = nstimespec(statb.st_mtim); |
| |
| // Rename the child, and both the source and dest |
| // directories should be updated |
| zx_nanosleep(zx_deadline_after(test_info->nsec_granularity)); |
| ASSERT_EQ(rename("::parent/child", "::parent2/child"), 0, ""); |
| ASSERT_EQ(stat("::parent", &statb), 0, ""); |
| ASSERT_GT(nstimespec(statb.st_mtim), now, ""); |
| ASSERT_EQ(stat("::parent2", &statb), 0, ""); |
| ASSERT_GT(nstimespec(statb.st_mtim), now, ""); |
| |
| // Clean up |
| ASSERT_EQ(unlink("::parent2/child"), 0, ""); |
| ASSERT_EQ(rmdir("::parent2"), 0, ""); |
| ASSERT_EQ(rmdir("::parent"), 0, ""); |
| |
| END_TEST; |
| } |
| |
| // clang-format off |
| RUN_FOR_ALL_FILESYSTEMS( |
| attr_tests, |
| RUN_TEST_MEDIUM(test_create_mtime) |
| RUN_TEST_MEDIUM(test_utimes_mtime) |
| RUN_TEST_MEDIUM(test_write_mtime) |
| RUN_TEST_MEDIUM(test_blksize) |
| RUN_TEST_MEDIUM(test_parent_directory_time) |
| ) |
| // clang-format on |