1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Copyright (c) 2008 Michael Kerrisk <mtk.manpages@gmail.com>
4 * Copyright (c) 2008 Subrata Modak <subrata@linux.vnet.ibm.com>
5 * Copyright (c) 2020 Viresh Kumar <viresh.kumar@linaro.org>
6 *
7 * Basic utimnsat() test.
8 */
9
10#define _GNU_SOURCE
11#include <stdio.h>
12#include <time.h>
13#include <errno.h>
14#include <stdlib.h>
15#include <unistd.h>
16#include <fcntl.h>
17#include <string.h>
18#include <sys/stat.h>
19#include "lapi/fs.h"
20#include "lapi/utime.h"
21#include "time64_variants.h"
22#include "tst_timer.h"
23
24#define MNTPOINT	"mntpoint"
25#define TEST_FILE	MNTPOINT"/test_file"
26#define TEST_DIR	MNTPOINT"/test_dir"
27
28static void *bad_addr;
29
30struct mytime {
31	long access_tv_sec;
32	long access_tv_nsec;
33	long mod_tv_sec;
34	long mod_tv_nsec;
35	int atime_change;
36	int mtime_change;
37};
38
39static struct mytime tnn = {0, UTIME_NOW, 0, UTIME_NOW, 1, 1};
40static struct mytime too = {0, UTIME_OMIT, 0, UTIME_OMIT, 0, 0};
41static struct mytime tno = {0, UTIME_NOW, 0, UTIME_OMIT, 1, 0};
42static struct mytime ton = {0, UTIME_OMIT, 0, UTIME_NOW, 0, 1};
43static struct mytime t11 = {1, 1, 1, 1, 1, 1};
44
45static struct test_case {
46	int dirfd;
47	char *pathname;
48	struct mytime *mytime;
49	int flags;
50	int oflags;
51	int attr;
52	int mode;
53	int exp_err;
54} tcase[] = {
55	/* Testing read-only file */
56	{AT_FDCWD, TEST_FILE, NULL, 0, O_RDONLY, 0, 0400, 0},
57	{AT_FDCWD, TEST_FILE, &tnn, 0, O_RDONLY, 0, 0400, 0},
58	{AT_FDCWD, TEST_FILE, &too, 0, O_RDONLY, 0, 0400, 0},
59	{AT_FDCWD, TEST_FILE, &tno, 0, O_RDONLY, 0, 0400, 0},
60	{AT_FDCWD, TEST_FILE, &ton, 0, O_RDONLY, 0, 0400, 0},
61	{AT_FDCWD, TEST_FILE, &t11, 0, O_RDONLY, 0, 0400, 0},
62
63	/* Testing writable file */
64	{AT_FDCWD, TEST_FILE, NULL, 0, O_RDONLY, 0, 0666, 0},
65	{AT_FDCWD, TEST_FILE, &tnn, 0, O_RDONLY, 0, 0666, 0},
66	{AT_FDCWD, TEST_FILE, &too, 0, O_RDONLY, 0, 0666, 0},
67	{AT_FDCWD, TEST_FILE, &tno, 0, O_RDONLY, 0, 0666, 0},
68	{AT_FDCWD, TEST_FILE, &ton, 0, O_RDONLY, 0, 0666, 0},
69	{AT_FDCWD, TEST_FILE, &t11, 0, O_RDONLY, 0, 0666, 0},
70
71	/* Testing append-only file */
72	{AT_FDCWD, TEST_FILE, NULL, 0, O_RDONLY, FS_APPEND_FL, 0600, 0},
73	{AT_FDCWD, TEST_FILE, &tnn, 0, O_RDONLY, FS_APPEND_FL, 0600, 0},
74	{AT_FDCWD, TEST_FILE, &too, 0, O_RDONLY, FS_APPEND_FL, 0600, 0},
75	{AT_FDCWD, TEST_FILE, &tno, 0, O_RDONLY, FS_APPEND_FL, 0600, EPERM},
76	{AT_FDCWD, TEST_FILE, &ton, 0, O_RDONLY, FS_APPEND_FL, 0600, EPERM},
77	{AT_FDCWD, TEST_FILE, &t11, 0, O_RDONLY, FS_APPEND_FL, 0600, EPERM},
78
79	/* Testing immutable file */
80	{AT_FDCWD, TEST_FILE, NULL, 0, O_RDONLY, FS_IMMUTABLE_FL, 0600, -1},
81	{AT_FDCWD, TEST_FILE, &tnn, 0, O_RDONLY, FS_IMMUTABLE_FL, 0600, -1},
82	{AT_FDCWD, TEST_FILE, &too, 0, O_RDONLY, FS_IMMUTABLE_FL, 0600, 0},
83	{AT_FDCWD, TEST_FILE, &tno, 0, O_RDONLY, FS_IMMUTABLE_FL, 0600, EPERM},
84	{AT_FDCWD, TEST_FILE, &ton, 0, O_RDONLY, FS_IMMUTABLE_FL, 0600, EPERM},
85	{AT_FDCWD, TEST_FILE, &t11, 0, O_RDONLY, FS_IMMUTABLE_FL, 0600, EPERM},
86
87	/* Testing immutable-append-only file */
88	{AT_FDCWD, TEST_FILE, NULL, 0, O_RDONLY, FS_APPEND_FL|FS_IMMUTABLE_FL, 0600, -1},
89	{AT_FDCWD, TEST_FILE, &tnn, 0, O_RDONLY, FS_APPEND_FL|FS_IMMUTABLE_FL, 0600, -1},
90	{AT_FDCWD, TEST_FILE, &too, 0, O_RDONLY, FS_APPEND_FL|FS_IMMUTABLE_FL, 0600, 0},
91	{AT_FDCWD, TEST_FILE, &tno, 0, O_RDONLY, FS_APPEND_FL|FS_IMMUTABLE_FL, 0600, EPERM},
92	{AT_FDCWD, TEST_FILE, &ton, 0, O_RDONLY, FS_APPEND_FL|FS_IMMUTABLE_FL, 0600, EPERM},
93	{AT_FDCWD, TEST_FILE, &t11, 0, O_RDONLY, FS_APPEND_FL|FS_IMMUTABLE_FL, 0600, EPERM},
94
95	/* Other failure tests */
96	{AT_FDCWD, TEST_FILE, NULL, 0, O_RDONLY, 0, 0400, EFAULT},
97	{AT_FDCWD, NULL, &tnn, 0, O_RDONLY, 0, 0400, EFAULT},
98	{-1, NULL, &tnn, AT_SYMLINK_NOFOLLOW, O_RDONLY, 0, 0400, EINVAL},
99	{-1, TEST_FILE, &tnn, 0, O_RDONLY, 0, 0400, ENOENT},
100};
101
102static inline int sys_utimensat(int dirfd, const char *pathname,
103				void *times, int flags)
104{
105	return tst_syscall(__NR_utimensat, dirfd, pathname, times, flags);
106}
107
108static inline int sys_utimensat_time64(int dirfd, const char *pathname,
109				       void *times, int flags)
110{
111	return tst_syscall(__NR_utimensat_time64, dirfd, pathname, times, flags);
112}
113
114static struct time64_variants variants[] = {
115#if (__NR_utimensat != __LTP__NR_INVALID_SYSCALL)
116	{ .utimensat = sys_utimensat, .ts_type = TST_KERN_OLD_TIMESPEC, .desc = "syscall with old kernel spec"},
117#endif
118
119#if (__NR_utimensat_time64 != __LTP__NR_INVALID_SYSCALL)
120	{ .utimensat = sys_utimensat_time64, .ts_type = TST_KERN_TIMESPEC, .desc = "syscall time64 with kernel spec"},
121#endif
122};
123
124static union tst_multi {
125	struct timespec libc_ts[2];
126	struct __kernel_old_timespec kern_old_ts[2];
127	struct __kernel_timespec kern_ts[2];
128} ts;
129
130static void multi_set_time(enum tst_ts_type type, struct mytime *mytime)
131{
132	switch (type) {
133	case TST_LIBC_TIMESPEC:
134		ts.libc_ts[0].tv_sec = mytime->access_tv_sec;
135		ts.libc_ts[0].tv_nsec = mytime->access_tv_nsec;
136		ts.libc_ts[1].tv_sec = mytime->mod_tv_sec;
137		ts.libc_ts[1].tv_nsec = mytime->mod_tv_nsec;
138	break;
139	case TST_KERN_OLD_TIMESPEC:
140		ts.kern_old_ts[0].tv_sec = mytime->access_tv_sec;
141		ts.kern_old_ts[0].tv_nsec = mytime->access_tv_nsec;
142		ts.kern_old_ts[1].tv_sec = mytime->mod_tv_sec;
143		ts.kern_old_ts[1].tv_nsec = mytime->mod_tv_nsec;
144	break;
145	case TST_KERN_TIMESPEC:
146		ts.kern_ts[0].tv_sec = mytime->access_tv_sec;
147		ts.kern_ts[0].tv_nsec = mytime->access_tv_nsec;
148		ts.kern_ts[1].tv_sec = mytime->mod_tv_sec;
149		ts.kern_ts[1].tv_nsec = mytime->mod_tv_nsec;
150	break;
151	default:
152		tst_brk(TBROK, "Invalid type: %d", type);
153	}
154}
155
156static void update_error(struct test_case *tc)
157{
158	static struct tst_kern_exv kvers[] = {
159		/* Ubuntu kernel has patch b3b4283 since 4.4.0-48.69 */
160		{ "UBUNTU", "4.4.0-48.69" },
161		{ NULL, NULL},
162	};
163
164	if (tc->exp_err != -1)
165		return;
166
167	/*
168	 * Starting with 4.8.0 operations on immutable files return EPERM
169	 * instead of EACCES.
170	 * This patch has also been merged to stable 4.4 with
171	 * b3b4283 ("vfs: move permission checking into notify_change() for utimes(NULL)")
172	 */
173	if (tst_kvercmp2(4, 4, 27, kvers) < 0)
174		tc->exp_err = EACCES;
175	else
176		tc->exp_err = EPERM;
177}
178
179static void change_attr(struct test_case *tc, int fd, int set)
180{
181	int attr;
182
183	if (!tc->attr)
184		return;
185
186	if (ioctl(fd, FS_IOC_GETFLAGS, &attr)) {
187		if (errno == ENOTTY)
188			tst_brk(TCONF | TERRNO, "Attributes not supported by FS");
189		else
190			tst_brk(TBROK | TERRNO, "ioctl(fd, FS_IOC_GETFLAGS, &attr) failed");
191	}
192
193	if (set)
194		attr |= tc->attr;
195	else
196		attr &= ~tc->attr;
197
198	SAFE_IOCTL(fd, FS_IOC_SETFLAGS, &attr);
199}
200
201static void reset_time(char *pathname, int dfd, int flags, int i)
202{
203	struct time64_variants *tv = &variants[tst_variant];
204	struct stat sb;
205
206	memset(&ts, 0, sizeof(ts));
207	TEST(tv->utimensat(dfd, pathname, &ts, flags));
208	if (TST_RET) {
209		tst_res(TINFO | TTERRNO, "%2d: utimensat(%d, %s, {0, 0}, %d) failed",
210			i, dfd, pathname, flags);
211	}
212
213	TEST(stat(pathname, &sb));
214	if (TST_RET) {
215		tst_res(TFAIL | TTERRNO, "%2d: stat() failed", i);
216	} else if (sb.st_atime || sb.st_mtime) {
217		tst_res(TFAIL, "Failed to reset access and modification time (%lu: %lu)",
218			sb.st_atime, sb.st_mtime);
219	}
220}
221
222static void run(unsigned int i)
223{
224	struct time64_variants *tv = &variants[tst_variant];
225	struct test_case *tc = &tcase[i];
226	int dfd = AT_FDCWD, fd = 0, atime_change, mtime_change;
227	struct mytime *mytime = tc->mytime;
228	char *pathname = NULL;
229	void *tsp = NULL;
230	struct stat sb;
231
232	if (tc->dirfd != AT_FDCWD)
233		dfd = SAFE_OPEN(TEST_DIR, tc->oflags);
234
235	if (tc->pathname) {
236		fd = SAFE_OPEN(tc->pathname, O_WRONLY | O_CREAT, 0200);
237		pathname = tc->pathname;
238		SAFE_CHMOD(tc->pathname, tc->mode);
239		reset_time(pathname, dfd, tc->flags, i);
240		change_attr(tc, fd, 1);
241	} else if (tc->exp_err == EFAULT) {
242		pathname = bad_addr;
243	}
244
245	if (mytime) {
246		multi_set_time(tv->ts_type, mytime);
247		tsp = &ts;
248	} else if (tc->exp_err == EFAULT) {
249		tsp = bad_addr;
250	}
251
252	TEST(tv->utimensat(dfd, pathname, tsp, tc->flags));
253	if (tc->pathname)
254		change_attr(tc, fd, 0);
255
256	if (TST_RET) {
257		if (!tc->exp_err) {
258			tst_res(TFAIL | TTERRNO, "%2d: utimensat() failed", i);
259		} else if (tc->exp_err == TST_ERR) {
260			tst_res(TPASS | TTERRNO, "%2d: utimensat() failed expectedly", i);
261		} else {
262			tst_res(TFAIL | TTERRNO, "%2d: utimensat() failed with incorrect error, expected %s",
263				i, tst_strerrno(tc->exp_err));
264		}
265	} else if (tc->exp_err) {
266		tst_res(TFAIL, "%2d: utimensat() passed unexpectedly", i);
267	} else {
268		atime_change = mytime ? mytime->atime_change : 1;
269		mtime_change = mytime ? mytime->mtime_change : 1;
270
271		TEST(stat(tc->pathname ? tc->pathname : TEST_DIR, &sb));
272		if (TST_RET) {
273			tst_res(TFAIL | TTERRNO, "%2d: stat() failed", i);
274			goto close;
275		}
276
277		if (!!sb.st_atime != atime_change) {
278			tst_res(TFAIL, "%2d: atime %s have changed but %s",
279				i, atime_change ? "should" : "shouldn't",
280				sb.st_atime ? "did" : "didn't");
281		} else if (!!sb.st_mtime != mtime_change) {
282			tst_res(TFAIL, "%2d: mtime %s have changed but %s",
283				i, mtime_change ? "should" : "shouldn't",
284				sb.st_mtime ? "did" : "didn't");
285		} else {
286			tst_res(TPASS, "%2d: utimensat() passed", i);
287		}
288	}
289
290close:
291	if (dfd != AT_FDCWD)
292		SAFE_CLOSE(dfd);
293
294	if (tc->pathname)
295		SAFE_CLOSE(fd);
296}
297
298static void setup(void)
299{
300	size_t i;
301
302	tst_res(TINFO, "Testing variant: %s", variants[tst_variant].desc);
303
304	bad_addr = tst_get_bad_addr(NULL);
305	if (access(TEST_DIR, R_OK))
306		SAFE_MKDIR(TEST_DIR, 0700);
307
308	for (i = 0; i < ARRAY_SIZE(tcase); i++)
309		update_error(&tcase[i]);
310}
311
312static struct tst_test test = {
313	.test = run,
314	.tcnt = ARRAY_SIZE(tcase),
315	.test_variants = ARRAY_SIZE(variants),
316	.setup = setup,
317	.needs_root = 1,
318	.mount_device = 1,
319	.mntpoint = MNTPOINT,
320};
321