1// Copyright 2017 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <fcntl.h>
6#include <math.h>
7#include <stdint.h>
8#include <stdio.h>
9#include <stdlib.h>
10#include <unistd.h>
11
12#include <sys/stat.h>
13#include <sys/time.h>
14
15#include <lib/fdio/vfs.h>
16#include <zircon/syscalls.h>
17#include <zircon/time.h>
18#include <zircon/types.h>
19
20#include "filesystems.h"
21
22#define ROUND_DOWN(t, granularity) ((t) - ((t) % (granularity)))
23
24zx_time_t nstimespec(struct timespec ts) {
25    // assumes very small number of seconds in deltas
26    return zx_time_add_duration(ZX_SEC(ts.tv_sec), ts.tv_nsec);
27}
28
29bool test_attr(void) {
30    BEGIN_TEST;
31    zx_time_t now = zx_clock_get(ZX_CLOCK_UTC);
32    ASSERT_NE(now, 0u, "zx_clock_get only returns zero on error");
33
34    int fd1 = open("::file.txt", O_CREAT | O_RDWR, 0644);
35    ASSERT_GT(fd1, 0, "");
36
37    struct timespec ts[2];
38    ts[0].tv_nsec = UTIME_OMIT;
39    ts[1].tv_sec = (long)(now / ZX_SEC(1));
40    ts[1].tv_nsec = (long)(now % ZX_SEC(1));
41
42    // make sure we get back "now" from stat()
43    ASSERT_EQ(futimens(fd1, ts), 0, "");
44    struct stat statb1;
45    ASSERT_EQ(fstat(fd1, &statb1), 0, "");
46    now = ROUND_DOWN(now, test_info->nsec_granularity);
47    ASSERT_EQ(statb1.st_mtim.tv_sec, (long)(now / ZX_SEC(1)), "");
48    ASSERT_EQ(statb1.st_mtim.tv_nsec, (long)(now % ZX_SEC(1)), "");
49    ASSERT_EQ(close(fd1), 0, "");
50
51    zx_nanosleep(zx_deadline_after(test_info->nsec_granularity));
52
53    ASSERT_EQ(utimes("::file.txt", NULL), 0, "");
54    struct stat statb2;
55    ASSERT_EQ(stat("::file.txt", &statb2), 0, "");
56    ASSERT_GT(nstimespec(statb2.st_mtim), nstimespec(statb1.st_mtim), "");
57
58    ASSERT_EQ(unlink("::file.txt"), 0, "");
59
60    END_TEST;
61}
62
63bool test_blksize(void) {
64    BEGIN_TEST;
65
66    int fd = open("::file.txt", O_CREAT | O_RDWR, 0644);
67    ASSERT_GT(fd, 0, "");
68
69    struct stat buf;
70    ASSERT_EQ(fstat(fd, &buf), 0, "");
71    ASSERT_GT(buf.st_blksize, 0, "blksize should be greater than zero");
72    ASSERT_EQ(buf.st_blksize % VNATTR_BLKSIZE, 0, "blksize should be a multiple of VNATTR_BLKSIZE");
73    ASSERT_EQ(buf.st_blocks, 0, "Number of allocated blocks should be zero");
74
75    char data = {'a'};
76    ASSERT_EQ(write(fd, &data, 1), 1, "Couldn't write a single byte to file");
77    ASSERT_EQ(fstat(fd, &buf), 0, "");
78    ASSERT_GT(buf.st_blksize, 0, "blksize should be greater than zero");
79    ASSERT_EQ(buf.st_blksize % VNATTR_BLKSIZE, 0, "blksize should be a multiple of VNATTR_BLKSIZE");
80    ASSERT_GT(buf.st_blocks, 0, "Number of allocated blocks should greater than zero");
81    ASSERT_EQ(close(fd), 0, "");
82
83    blkcnt_t nblocks = buf.st_blocks;
84    ASSERT_EQ(stat("::file.txt", &buf), 0, "");
85    ASSERT_EQ(buf.st_blocks, nblocks, "Block count changed when closing file");
86
87    ASSERT_EQ(unlink("::file.txt"), 0, "");
88
89    END_TEST;
90}
91
92bool test_parent_directory_time(void) {
93    BEGIN_TEST;
94
95    if (strcmp(test_info->name, "FAT") == 0) {
96        // FAT does not update parent directory times when children
97        // are updated
98        printf("FAT parent directory timestamps aren't updated; skipping test...\n");
99        return true;
100    }
101
102    zx_time_t now = zx_clock_get(ZX_CLOCK_UTC);
103    ASSERT_NE(now, 0u, "zx_clock_get only returns zero on error");
104
105    // Create a parent directory to contain new contents
106    zx_nanosleep(zx_deadline_after(test_info->nsec_granularity));
107    ASSERT_EQ(mkdir("::parent", 0666), 0, "");
108    ASSERT_EQ(mkdir("::parent2", 0666), 0, "");
109
110    // Ensure the parent directory's create + modified times
111    // were initialized correctly.
112    struct stat statb;
113    ASSERT_EQ(stat("::parent", &statb), 0, "");
114    ASSERT_GT(nstimespec(statb.st_ctim), now, "");
115    ASSERT_GT(nstimespec(statb.st_mtim), now, "");
116    now = nstimespec(statb.st_ctim);
117
118    // Create a file in the parent directory
119    zx_nanosleep(zx_deadline_after(test_info->nsec_granularity));
120    int fd = open("::parent/child", O_CREAT | O_RDWR);
121    ASSERT_GT(fd, 0, "");
122    ASSERT_EQ(close(fd), 0, "");
123
124    // Time moved forward in both the child...
125    ASSERT_EQ(stat("::parent/child", &statb), 0, "");
126    ASSERT_GT(nstimespec(statb.st_mtim), now, "");
127    // ... and the parent
128    ASSERT_EQ(stat("::parent", &statb), 0, "");
129    ASSERT_GT(nstimespec(statb.st_mtim), now, "");
130    now = nstimespec(statb.st_mtim);
131
132    // Link the child into a second directory
133    zx_nanosleep(zx_deadline_after(test_info->nsec_granularity));
134    ASSERT_EQ(link("::parent/child", "::parent2/child"), 0, "");
135    // Source directory is not impacted
136    ASSERT_EQ(stat("::parent", &statb), 0, "");
137    ASSERT_EQ(nstimespec(statb.st_mtim), now, "");
138    // Target directory is updated
139    ASSERT_EQ(stat("::parent2", &statb), 0, "");
140    ASSERT_GT(nstimespec(statb.st_mtim), now, "");
141    now = nstimespec(statb.st_mtim);
142
143    // Unlink the child, and the parent's time should
144    // move forward again
145    zx_nanosleep(zx_deadline_after(test_info->nsec_granularity));
146    ASSERT_EQ(unlink("::parent2/child"), 0, "");
147    ASSERT_EQ(stat("::parent2", &statb), 0, "");
148    ASSERT_GT(nstimespec(statb.st_mtim), now, "");
149    now = nstimespec(statb.st_mtim);
150
151    // Rename the child, and both the source and dest
152    // directories should be updated
153    zx_nanosleep(zx_deadline_after(test_info->nsec_granularity));
154    ASSERT_EQ(rename("::parent/child", "::parent2/child"), 0, "");
155    ASSERT_EQ(stat("::parent", &statb), 0, "");
156    ASSERT_GT(nstimespec(statb.st_mtim), now, "");
157    ASSERT_EQ(stat("::parent2", &statb), 0, "");
158    ASSERT_GT(nstimespec(statb.st_mtim), now, "");
159
160    // Clean up
161    ASSERT_EQ(unlink("::parent2/child"), 0, "");
162    ASSERT_EQ(rmdir("::parent2"), 0, "");
163    ASSERT_EQ(rmdir("::parent"), 0, "");
164
165    END_TEST;
166}
167
168RUN_FOR_ALL_FILESYSTEMS(attr_tests,
169    RUN_TEST_MEDIUM(test_attr)
170    RUN_TEST_MEDIUM(test_blksize)
171    RUN_TEST_MEDIUM(test_parent_directory_time)
172)
173