1 /*
2  * This file and its contents are supplied under the terms of the
3  * Common Development and Distribution License ("CDDL"), version 1.0.
4  * You may only use this file in accordance with the terms of version
5  * 1.0 of the CDDL.
6  *
7  * A full copy of the text of the CDDL should have accompanied this
8  * source.  A copy of the CDDL is also available via the Internet at
9  * http://www.illumos.org/license/CDDL.
10  */
11 
12 /*
13  * Copyright 2020 OmniOS Community Edition (OmniOSce) Association.
14  */
15 
16 /*
17  * Test the implementation of the various *utimes() and *utimens() functions
18  */
19 
20 #include <stdio.h>
21 #include <stdbool.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <err.h>
25 #include <sys/sysmacros.h>
26 #include <sys/types.h>
27 #include <sys/time.h>
28 #include <sys/stat.h>
29 #include <fcntl.h>
30 #include <unistd.h>
31 #include <errno.h>
32 
33 timespec_t testtimes[] = {
34 	{
35 		.tv_sec = 1280793678,
36 		.tv_nsec = 123456789
37 	},
38 	{
39 		.tv_sec = 1492732800,
40 		.tv_nsec = 17
41 	},
42 	{
43 		.tv_sec = 1320796855,
44 		.tv_nsec = 9
45 	},
46 	{
47 		.tv_sec = 1498953611,
48 		.tv_nsec = 987654321
49 	}
50 };
51 
52 enum ttype {
53 	UTIMES,
54 	LUTIMES,
55 	FUTIMES,
56 	FUTIMESAT,
57 	FUTIMENS,
58 	UTIMENSAT
59 };
60 
61 static bool
compare_times(struct stat * st,bool trunc,timespec_t * atim,timespec_t * mtim,bool invert)62 compare_times(struct stat *st, bool trunc, timespec_t *atim, timespec_t *mtim,
63     bool invert)
64 {
65 	bool ret = true;
66 
67 	if (st->st_atim.tv_sec != atim->tv_sec) {
68 		ret = false;
69 	} else if (st->st_atim.tv_nsec != (
70 	    trunc ? atim->tv_nsec / 1000 * 1000 : atim->tv_nsec)) {
71 		ret = false;
72 	} else if (st->st_mtim.tv_sec != mtim->tv_sec) {
73 		ret = false;
74 	} else if (st->st_mtim.tv_nsec != (
75 	    trunc ? mtim->tv_nsec / 1000 * 1000 : mtim->tv_nsec)) {
76 		ret = false;
77 	}
78 
79 	if ((!ret && !invert) || (ret && invert)) {
80 		printf("    actual atime: %ld.%.9ld\n",
81 		    st->st_atim.tv_sec, st->st_atim.tv_nsec);
82 		printf("    actual mtime: %ld.%.9ld\n",
83 		    st->st_mtim.tv_sec, st->st_mtim.tv_nsec);
84 	}
85 
86 	return (ret);
87 }
88 
89 static bool
compare_filetime(char * path,bool trunc,timespec_t * atim,timespec_t * mtim,bool invert)90 compare_filetime(char *path, bool trunc, timespec_t *atim, timespec_t *mtim,
91     bool invert)
92 {
93 	struct stat st;
94 
95 	if (stat(path, &st) == -1)
96 		err(EXIT_FAILURE, "stat %s", path);
97 
98 	return (compare_times(&st, trunc, atim, mtim, invert));
99 }
100 
101 static bool
compare_linktime(char * path,bool trunc,timespec_t * atim,timespec_t * mtim,bool invert)102 compare_linktime(char *path, bool trunc, timespec_t *atim, timespec_t *mtim,
103     bool invert)
104 {
105 	struct stat st;
106 
107 	if (lstat(path, &st) == -1)
108 		err(EXIT_FAILURE, "lstat %s", path);
109 
110 	return (compare_times(&st, trunc, atim, mtim, invert));
111 }
112 
113 static bool
reset(char * path,timespec_t * atim,timespec_t * mtim)114 reset(char *path, timespec_t *atim, timespec_t *mtim)
115 {
116 	if (utimes(path, NULL) == -1)
117 		err(EXIT_FAILURE, "utimes reset");
118 	if (compare_filetime(path, true, atim, mtim, true)) {
119 		warnx("reset failed");
120 		return (false);
121 	}
122 	return (true);
123 }
124 
125 static bool
reset_link(char * lpath,timespec_t * atim,timespec_t * mtim)126 reset_link(char *lpath, timespec_t *atim, timespec_t *mtim)
127 {
128 	if (lutimes(lpath, NULL) == -1)
129 		err(EXIT_FAILURE, "lutimes reset");
130 	if (compare_linktime(lpath, true, atim, mtim, true)) {
131 		warnx("link reset failed");
132 		return (false);
133 	}
134 	return (true);
135 }
136 
137 static bool
runtest(enum ttype fn,char * dir,timespec_t * atim,timespec_t * mtim)138 runtest(enum ttype fn, char *dir, timespec_t *atim, timespec_t *mtim)
139 {
140 	char path[MAXPATHLEN + 1];
141 	char lpath[MAXPATHLEN + 1];
142 	struct timespec ts[2];
143 	struct timeval tv[2];
144 	int fd, lfd, dfd, ret = true;
145 
146 	ts[0] = *atim;
147 	ts[1] = *mtim;
148 	TIMESPEC_TO_TIMEVAL(&tv[0], &ts[0]);
149 	TIMESPEC_TO_TIMEVAL(&tv[1], &ts[1]);
150 
151 	if (snprintf(path, sizeof (path), "%s/file", dir) >= sizeof (path))
152 		err(EXIT_FAILURE, "snprintf failed to build file path");
153 
154 	if ((fd = open(path, O_CREAT, 0644)) == -1)
155 		err(EXIT_FAILURE, "open file %s", path);
156 
157 	if (snprintf(lpath, sizeof (lpath), "%s/link", dir) >= sizeof (path))
158 		err(EXIT_FAILURE, "snprintf failed to build link path");
159 
160 	if (symlink(path, lpath) == -1)
161 		err(EXIT_FAILURE, "link(%s)", lpath);
162 
163 	if ((lfd = open(lpath, O_RDWR)) == -1)
164 		err(EXIT_FAILURE, "open link(%s)", lpath);
165 
166 	if ((dfd = open(dir, O_DIRECTORY|O_RDONLY)) == -1)
167 		err(EXIT_FAILURE, "open dir(%s)", dir);
168 
169 	switch (fn) {
170 	case UTIMES:
171 		printf("..... utimes()\n");
172 
173 		if (utimes(path, tv) == -1)
174 			err(EXIT_FAILURE, "utimes(%s)", path);
175 		if (!compare_filetime(path, true, atim, mtim, false)) {
176 			warnx("failed on file");
177 			ret = false;
178 		}
179 
180 		if (!reset(path, atim, mtim))
181 			ret = false;
182 
183 		/* repeat against symbolic link path */
184 		if (utimes(lpath, tv) == -1)
185 			err(EXIT_FAILURE, "utimes(%s), link", lpath);
186 		if (!compare_filetime(path, true, atim, mtim, false)) {
187 			warnx("failed on file through link");
188 			ret = false;
189 		}
190 
191 		break;
192 
193 	case LUTIMES:
194 		printf("..... lutimes()\n");
195 
196 		/* Use lutimes() against a plain file */
197 		if (lutimes(path, tv) == -1)
198 			err(EXIT_FAILURE, "lutimes(%s)", path);
199 		if (!compare_filetime(path, true, atim, mtim, false)) {
200 			warnx("failed on file");
201 			ret = false;
202 		}
203 
204 		if (!reset(path, atim, mtim))
205 			ret = false;
206 
207 		/* Set the time on the link, not on the target */
208 		if (lutimes(lpath, tv) == -1)
209 			err(EXIT_FAILURE, "lutimes(%s)", lpath);
210 		if (!compare_linktime(lpath, true, atim, mtim, false)) {
211 			warnx("link time is incorrect");
212 			ret = false;
213 		}
214 		if (compare_filetime(path, true, atim, mtim, true)) {
215 			warnx("target time was updated incorrectly");
216 			ret = false;
217 		}
218 
219 		/* Reset the time on the path and link to the current time */
220 		if (!reset(path, atim, mtim) || !reset_link(lpath, atim, mtim))
221 			ret = false;
222 
223 		/* and modify the target */
224 		if (utimes(path, tv) == -1)
225 			err(EXIT_FAILURE, "utimes(%s)", path);
226 		/* Now the target should match but the link should not */
227 		if (!compare_filetime(path, true, atim, mtim, false)) {
228 			warnx("target time is incorrect");
229 			ret = false;
230 		}
231 		if (compare_linktime(lpath, true, atim, mtim, true)) {
232 			warnx("link time was updated incorrectly");
233 			ret = false;
234 		}
235 		break;
236 
237 	case FUTIMES:
238 		printf("..... futimes()\n");
239 
240 		if (futimes(fd, tv) == -1)
241 			err(EXIT_FAILURE, "futimes(%s)", path);
242 		if (!compare_filetime(path, true, atim, mtim, false)) {
243 			warnx("failed on file");
244 			ret = false;
245 		}
246 
247 		break;
248 
249 	case FUTIMESAT: {
250 		int rfd;
251 		printf("..... futimesat()\n");
252 
253 		/* NULL path, should modify the file for 'fd' */
254 		if (futimesat(fd, NULL, tv) == -1)
255 			err(EXIT_FAILURE, "futimesat(fd, NULL)");
256 		if (!compare_filetime(path, true, atim, mtim, false)) {
257 			warnx("failed with null path");
258 			ret = false;
259 		}
260 
261 		if (!reset(path, atim, mtim))
262 			ret = false;
263 
264 		/* random descriptor, FQ path, descriptor is ignored */
265 		if ((rfd = open("/dev/null", O_RDONLY)) == -1)
266 			err(EXIT_FAILURE, "open(/dev/null)");
267 		if (futimesat(rfd, path, tv) == -1)
268 			err(EXIT_FAILURE, "futimesat(dnfd, %s)", path);
269 		if (!compare_filetime(path, true, atim, mtim, false)) {
270 			warnx("failed with random descriptor and fq path");
271 			ret = false;
272 		}
273 
274 		if (!reset(path, atim, mtim))
275 			ret = false;
276 
277 		/* repeat against symbolic link path */
278 		if (futimesat(rfd, lpath, tv) == -1)
279 			err(EXIT_FAILURE, "futimesat(dnfd, %s), link", lpath);
280 		if (!compare_filetime(path, true, atim, mtim, false)) {
281 			warnx("failed through link with "
282 			    "random descriptor, fq path");
283 			ret = false;
284 		}
285 
286 		(void) close(rfd);
287 
288 		if (!reset(path, atim, mtim))
289 			ret = false;
290 
291 		/* relative to a directory */
292 		if (futimesat(dfd, "file", tv) == -1)
293 			err(EXIT_FAILURE, "futimesat(dir, file)", path);
294 		if (!compare_filetime(path, true, atim, mtim, false)) {
295 			warnx("failed relative to a directory");
296 			ret = false;
297 		}
298 
299 		if (!reset(path, atim, mtim))
300 			ret = false;
301 
302 		/* repeat against symbolic link path */
303 		if (futimesat(dfd, "link", tv) == -1)
304 			err(EXIT_FAILURE, "futimesat(dir, link)");
305 		if (!compare_filetime(path, true, atim, mtim, false)) {
306 			warnx("failed through link relative to a directory");
307 			ret = false;
308 		}
309 
310 		if (!reset(path, atim, mtim))
311 			ret = false;
312 
313 		/* AT_FDCWD */
314 		if (fchdir(dfd) == -1)
315 			err(EXIT_FAILURE, "fchdir(%s)", dir);
316 		if (futimesat(AT_FDCWD, "file", tv) == -1)
317 			err(EXIT_FAILURE, "futimesat(AT_FDCWD, file)", path);
318 		if (!compare_filetime(path, true, atim, mtim, false)) {
319 			warnx("failed with AT_FDCWD relative");
320 			ret = false;
321 		}
322 
323 		if (!reset(path, atim, mtim))
324 			ret = false;
325 
326 		/* repeat against symbolic link path */
327 		if (futimesat(AT_FDCWD, "link", tv) == -1)
328 			err(EXIT_FAILURE, "futimesat(AT_FDCWD, link)");
329 		if (!compare_filetime(path, true, atim, mtim, false)) {
330 			warnx("failed through link with AT_FDCWD relative");
331 			ret = false;
332 		}
333 
334 		break;
335 	}
336 
337 	case FUTIMENS:
338 		printf("..... futimens()\n");
339 		if (futimens(fd, ts) == -1)
340 			err(EXIT_FAILURE, "futimesns(%s)", path);
341 		if (!compare_filetime(path, false, atim, mtim, false)) {
342 			warnx("failed with plain file fd");
343 			ret = false;
344 		}
345 
346 		break;
347 
348 	case UTIMENSAT: {
349 		int rfd;
350 
351 		printf("..... utimensat()\n");
352 
353 		/* NULL path, expect EFAULT (cf. futimesat()) */
354 		if (utimensat(fd, NULL, ts, 0) != -1 || errno != EFAULT) {
355 			warnx("null path should return EFAULT but got %d/%s",
356 			    errno, strerror(errno));
357 			ret = false;
358 		}
359 
360 		/* random descriptor, FQ path, descriptor is ignored */
361 		if ((rfd = open("/dev/null", O_RDONLY)) == -1)
362 			err(EXIT_FAILURE, "open(/dev/null)");
363 		if (utimensat(rfd, path, ts, 0) == -1)
364 			err(EXIT_FAILURE, "utimensat(dnfd, %s)", path);
365 		if (!compare_filetime(path, false, atim, mtim, false)) {
366 			warnx("failed with random descriptor, fq path");
367 			ret = false;
368 		}
369 
370 		if (!reset(path, atim, mtim))
371 			ret = false;
372 
373 		/* repeat against symbolic link path */
374 		if (utimensat(rfd, lpath, ts, 0) == -1)
375 			err(EXIT_FAILURE, "utimensat(dnfd, link %s)", lpath);
376 		if (!compare_filetime(path, false, atim, mtim, false)) {
377 			warnx("failed against link with random descriptor, "
378 			    "fq path");
379 			ret = false;
380 		}
381 
382 		(void) close(rfd);
383 
384 		if (!reset(path, atim, mtim))
385 			ret = false;
386 
387 		/* relative to a directory */
388 		if (utimensat(dfd, "file", ts, 0) == -1)
389 			err(EXIT_FAILURE, "utimensat(dir, file)", path);
390 		if (!compare_filetime(path, false, atim, mtim, false)) {
391 			warnx("failed relative to a directory");
392 			ret = false;
393 		}
394 
395 		if (!reset(path, atim, mtim))
396 			ret = false;
397 
398 		/* repeat against symbolic link path */
399 		if (utimensat(dfd, "link", ts, 0) == -1)
400 			err(EXIT_FAILURE, "utimensat(dir, link)", path);
401 		if (!compare_filetime(path, false, atim, mtim, false)) {
402 			warnx("failed through link relative to a directory");
403 			ret = false;
404 		}
405 
406 		if (!reset(path, atim, mtim))
407 			ret = false;
408 
409 		/* AT_FDCWD */
410 		if (fchdir(dfd) == -1)
411 			err(EXIT_FAILURE, "fchdir(%s)", dir);
412 		if (utimensat(AT_FDCWD, "file", ts, 0) == -1)
413 			err(EXIT_FAILURE, "utimensat(AT_FDCWD, file)");
414 		if (!compare_filetime(path, false, atim, mtim, false)) {
415 			warnx("failed with AT_FDCWD relative");
416 			ret = false;
417 		}
418 
419 		if (!reset(path, atim, mtim))
420 			ret = false;
421 
422 		/* repeat against symbolic link path */
423 		if (utimensat(AT_FDCWD, "link", ts, 0) == -1)
424 			err(EXIT_FAILURE, "utimensat(AT_FDCWD, link)");
425 		if (!compare_filetime(path, false, atim, mtim, false)) {
426 			warnx("failed through link with AT_FDCWD relative");
427 			ret = false;
428 		}
429 
430 		if (!reset(path, atim, mtim))
431 			ret = false;
432 
433 		/*
434 		 * Check that none of the above operations have changed the
435 		 * timestamp on the link.
436 		 */
437 		if (compare_linktime(lpath, true, atim, mtim, true)) {
438 			warnx("link timestamp was unexpectedly modified");
439 			ret = false;
440 		}
441 
442 		/* Set the time on the link, not on the target */
443 		if (utimensat(AT_FDCWD, "link", ts, AT_SYMLINK_NOFOLLOW) == -1)
444 			err(EXIT_FAILURE, "utimensat(AT_FDCWD, lflag)");
445 		if (!compare_linktime(lpath, false, atim, mtim, false)) {
446 			warnx("link time is incorrect");
447 			ret = false;
448 		}
449 		if (compare_filetime(path, false, atim, mtim, true)) {
450 			warnx("target time was updated incorrectly");
451 			ret = false;
452 		}
453 		}
454 		break;
455 	}
456 
457 	(void) close(dfd);
458 	(void) close(lfd);
459 	(void) close(fd);
460 
461 	if (unlink(lpath) == -1)
462 		err(EXIT_FAILURE, "unlink(%s)", lpath);
463 	if (unlink(path) == -1)
464 		err(EXIT_FAILURE, "unlink(%s)", path);
465 
466 	if (!ret)
467 		warnx("Test failed");
468 
469 	return (ret);
470 }
471 
472 static bool
runtests(char * dir,timespec_t * atim,timespec_t * mtim)473 runtests(char *dir, timespec_t *atim, timespec_t *mtim)
474 {
475 	bool ret = true;
476 
477 	printf("Testing:\n... atime: %ld.%.9ld\n... mtime: %ld.%.9ld\n",
478 	    atim->tv_sec, atim->tv_nsec, mtim->tv_sec, mtim->tv_nsec);
479 
480 	if (!runtest(UTIMES, dir, atim, mtim))
481 		ret = false;
482 	if (!runtest(LUTIMES, dir, atim, mtim))
483 		ret = false;
484 	if (!runtest(FUTIMES, dir, atim, mtim))
485 		ret = false;
486 	if (!runtest(FUTIMESAT, dir, atim, mtim))
487 		ret = false;
488 	if (!runtest(FUTIMENS, dir, atim, mtim))
489 		ret = false;
490 	if (!runtest(UTIMENSAT, dir, atim, mtim))
491 		ret = false;
492 
493 	return (ret);
494 }
495 
496 int
main(void)497 main(void)
498 {
499 	char dir[] = "/tmp/utimes.XXXXXX";
500 	int ret = EXIT_SUCCESS;
501 	int i;
502 
503 	if (mkdtemp(dir) == NULL)
504 		err(EXIT_FAILURE, "failed to create temporary directory");
505 
506 	for (i = 0; i < ARRAY_SIZE(testtimes); i += 2) {
507 		if (!runtests(dir, &testtimes[i], &testtimes[i + 1]))
508 			ret = EXIT_FAILURE;
509 	}
510 
511 	/*
512 	 * Some tests will have changed directory into 'dir' to test the
513 	 * AT_FDCWD case. Change back to / to avoid EBUSY when removing dir.
514 	 */
515 	if (chdir("/") == -1)
516 		err(EXIT_FAILURE, "chdir(/)");
517 	if (rmdir(dir) == -1)
518 		err(EXIT_FAILURE, "rmdir %s", dir);
519 
520 	return (ret);
521 }
522