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
nstimespec(struct timespec ts)24 zx_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
test_attr(void)29 bool 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
test_blksize(void)63 bool 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
test_parent_directory_time(void)92 bool 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
168 RUN_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