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