blob: dc5eaf8511022a9c903e5caf440fb9a57863e915 [file] [log] [blame]
// 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