xref: /freebsd/sys/contrib/openzfs/tests/zfs-tests/cmd/ctime.c (revision 61145dc2b94f12f6a47344fb9aac702321880e43)
1 // SPDX-License-Identifier: CDDL-1.0
2 /*
3  * CDDL HEADER START
4  *
5  * The contents of this file are subject to the terms of the
6  * Common Development and Distribution License (the "License").
7  * You may not use this file except in compliance with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or https://opensource.org/licenses/CDDL-1.0.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 
23 /*
24  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
25  * Use is subject to license terms.
26  */
27 
28 /*
29  * Copyright (c) 2013 by Delphix. All rights reserved.
30  */
31 
32 
33 #include <sys/types.h>
34 #include <sys/stat.h>
35 #ifndef __FreeBSD__
36 #include <sys/xattr.h>
37 #endif
38 #include <utime.h>
39 #include <stdio.h>
40 #include <stdlib.h>
41 #include <unistd.h>
42 #include <errno.h>
43 #include <fcntl.h>
44 #include <libgen.h>
45 #include <string.h>
46 
47 #define	ST_ATIME 0
48 #define	ST_CTIME 1
49 #define	ST_MTIME 2
50 
51 #define	ALL_MODE (mode_t)(S_IRWXU|S_IRWXG|S_IRWXO)
52 
53 typedef struct timetest {
54 	int	type;
55 	const char	*name;
56 	int	(*func)(const char *pfile);
57 } timetest_t;
58 
59 static char tfile[BUFSIZ] = { 0 };
60 
61 /*
62  * DESCRIPTION:
63  * 	Verify time will be changed correctly after each operation.
64  *
65  * STRATEGY:
66  *	1. Define time test array.
67  *	2. Loop through each item in this array.
68  *	3. Verify the time is changed after each operation.
69  *
70  */
71 
72 static int
get_file_time(const char * pfile,int what,time_t * ptr)73 get_file_time(const char *pfile, int what, time_t *ptr)
74 {
75 	struct stat stat_buf;
76 
77 	if (pfile == NULL || ptr == NULL) {
78 		return (-1);
79 	}
80 
81 	if (stat(pfile, &stat_buf) == -1) {
82 		return (-1);
83 	}
84 
85 	switch (what) {
86 		case ST_ATIME:
87 			*ptr = stat_buf.st_atime;
88 			return (0);
89 		case ST_CTIME:
90 			*ptr = stat_buf.st_ctime;
91 			return (0);
92 		case ST_MTIME:
93 			*ptr = stat_buf.st_mtime;
94 			return (0);
95 		default:
96 			return (-1);
97 	}
98 }
99 
100 static ssize_t
get_dirnamelen(const char * path)101 get_dirnamelen(const char *path)
102 {
103 	const char *end = strrchr(path, '/');
104 	return (end ? end - path : -1);
105 }
106 
107 static int
do_read(const char * pfile)108 do_read(const char *pfile)
109 {
110 	int fd, ret = 0;
111 	char buf[BUFSIZ] = { 0 };
112 
113 	if (pfile == NULL) {
114 		return (-1);
115 	}
116 
117 	if ((fd = open(pfile, O_RDONLY, ALL_MODE)) == -1) {
118 		return (-1);
119 	}
120 	if (read(fd, buf, sizeof (buf)) == -1) {
121 		(void) fprintf(stderr, "read(%d, buf, %zd) failed with errno "
122 		    "%d\n", fd, sizeof (buf), errno);
123 		(void) close(fd);
124 		return (1);
125 	}
126 	(void) close(fd);
127 
128 	return (ret);
129 }
130 
131 static int
do_write(const char * pfile)132 do_write(const char *pfile)
133 {
134 	int fd, ret = 0;
135 	char buf[BUFSIZ] = "call function do_write()";
136 
137 	if (pfile == NULL) {
138 		return (-1);
139 	}
140 
141 	if ((fd = open(pfile, O_WRONLY, ALL_MODE)) == -1) {
142 		return (-1);
143 	}
144 	if (write(fd, buf, strlen(buf)) == -1) {
145 		(void) fprintf(stderr, "write(%d, buf, %d) failed with errno "
146 		    "%d\n", fd, (int)strlen(buf), errno);
147 		(void) close(fd);
148 		return (1);
149 	}
150 	(void) close(fd);
151 
152 	return (ret);
153 }
154 
155 static int
do_link(const char * pfile)156 do_link(const char *pfile)
157 {
158 	int ret = 0;
159 	char link_file[BUFSIZ + 16] = { 0 };
160 
161 	if (pfile == NULL) {
162 		return (-1);
163 	}
164 
165 	/*
166 	 * Figure out source file directory name, and create
167 	 * the link file in the same directory.
168 	 */
169 	(void) snprintf(link_file, sizeof (link_file),
170 	    "%.*s/%s", (int)get_dirnamelen(pfile), pfile, "link_file");
171 
172 	if (link(pfile, link_file) == -1) {
173 		(void) fprintf(stderr, "link(%s, %s) failed with errno %d\n",
174 		    pfile, link_file, errno);
175 		return (1);
176 	}
177 
178 	(void) unlink(link_file);
179 
180 	return (ret);
181 }
182 
183 static int
do_creat(const char * pfile)184 do_creat(const char *pfile)
185 {
186 	int fd, ret = 0;
187 
188 	if (pfile == NULL) {
189 		return (-1);
190 	}
191 
192 	if ((fd = creat(pfile, ALL_MODE)) == -1) {
193 		(void) fprintf(stderr, "creat(%s, ALL_MODE) failed with errno "
194 		    "%d\n", pfile, errno);
195 		return (1);
196 	}
197 	(void) close(fd);
198 
199 	return (ret);
200 }
201 
202 static int
do_utime(const char * pfile)203 do_utime(const char *pfile)
204 {
205 	int ret = 0;
206 
207 	if (pfile == NULL) {
208 		return (-1);
209 	}
210 
211 	/*
212 	 * Times of the file are set to the current time
213 	 */
214 	if (utime(pfile, NULL) == -1) {
215 		(void) fprintf(stderr, "utime(%s, NULL) failed with errno "
216 		    "%d\n", pfile, errno);
217 		return (1);
218 	}
219 
220 	return (ret);
221 }
222 
223 static int
do_chmod(const char * pfile)224 do_chmod(const char *pfile)
225 {
226 	int ret = 0;
227 
228 	if (pfile == NULL) {
229 		return (-1);
230 	}
231 
232 	if (chmod(pfile, ALL_MODE) == -1) {
233 		(void) fprintf(stderr, "chmod(%s, ALL_MODE) failed with "
234 		    "errno %d\n", pfile, errno);
235 		return (1);
236 	}
237 
238 	return (ret);
239 }
240 
241 static int
do_chown(const char * pfile)242 do_chown(const char *pfile)
243 {
244 	int ret = 0;
245 
246 	if (pfile == NULL) {
247 		return (-1);
248 	}
249 
250 	if (chown(pfile, getuid(), getgid()) == -1) {
251 		(void) fprintf(stderr, "chown(%s, %d, %d) failed with errno "
252 		    "%d\n", pfile, (int)getuid(), (int)getgid(), errno);
253 		return (1);
254 	}
255 
256 	return (ret);
257 }
258 
259 #ifndef __FreeBSD__
260 static int
do_xattr(const char * pfile)261 do_xattr(const char *pfile)
262 {
263 	int ret = 0;
264 	const char *value = "user.value";
265 
266 	if (pfile == NULL) {
267 		return (-1);
268 	}
269 
270 	if (setxattr(pfile, "user.x", value, strlen(value), 0) == -1) {
271 		(void) fprintf(stderr, "setxattr(%s, %d, %d) failed with errno "
272 		    "%d\n", pfile, (int)getuid(), (int)getgid(), errno);
273 		return (1);
274 	}
275 	return (ret);
276 }
277 #endif
278 
279 static void
cleanup(void)280 cleanup(void)
281 {
282 	if ((strlen(tfile) != 0) && (access(tfile, F_OK) == 0)) {
283 		(void) unlink(tfile);
284 	}
285 }
286 
287 static timetest_t timetest_table[] = {
288 	{ ST_ATIME,	"st_atime",	do_read		},
289 	{ ST_ATIME,	"st_atime",	do_utime	},
290 	{ ST_MTIME,	"st_mtime",	do_creat	},
291 	{ ST_MTIME,	"st_mtime",	do_write	},
292 	{ ST_MTIME,	"st_mtime",	do_utime	},
293 	{ ST_CTIME,	"st_ctime",	do_creat	},
294 	{ ST_CTIME,	"st_ctime",	do_write	},
295 	{ ST_CTIME,	"st_ctime",	do_chmod	},
296 	{ ST_CTIME,	"st_ctime",	do_chown 	},
297 	{ ST_CTIME,	"st_ctime",	do_link		},
298 	{ ST_CTIME,	"st_ctime",	do_utime	},
299 #ifndef __FreeBSD__
300 	{ ST_CTIME,	"st_ctime",	do_xattr	},
301 #endif
302 };
303 
304 #define	NCOMMAND (sizeof (timetest_table) / sizeof (timetest_table[0]))
305 
306 int
main(void)307 main(void)
308 {
309 	int i, ret, fd;
310 	const char *penv[] = {"TESTDIR", "TESTFILE0"};
311 
312 	(void) atexit(cleanup);
313 
314 	/*
315 	 * Get the environment variable values.
316 	 */
317 	for (i = 0; i < sizeof (penv) / sizeof (char *); i++) {
318 		if ((penv[i] = getenv(penv[i])) == NULL) {
319 			(void) fprintf(stderr, "getenv(penv[%d])\n", i);
320 			return (1);
321 		}
322 	}
323 	(void) snprintf(tfile, sizeof (tfile), "%s/%s", penv[0], penv[1]);
324 
325 	/*
326 	 * If the test file exists, remove it first.
327 	 */
328 	if (access(tfile, F_OK) == 0) {
329 		(void) unlink(tfile);
330 	}
331 	if ((fd = open(tfile, O_WRONLY | O_CREAT | O_TRUNC, ALL_MODE)) == -1) {
332 		(void) fprintf(stderr, "open(%s) failed: %d\n", tfile, errno);
333 		return (1);
334 	}
335 	(void) close(fd);
336 
337 	for (i = 0; i < NCOMMAND; i++) {
338 		time_t t1, t2;
339 
340 		/*
341 		 * Get original time before operating.
342 		 */
343 		ret = get_file_time(tfile, timetest_table[i].type, &t1);
344 		if (ret != 0) {
345 			(void) fprintf(stderr, "get_file_time(%s %d) = %d\n",
346 			    tfile, timetest_table[i].type, ret);
347 			return (1);
348 		}
349 
350 		/*
351 		 * Sleep 2 seconds, then invoke command on given file
352 		 */
353 		(void) sleep(2);
354 		timetest_table[i].func(tfile);
355 
356 		/*
357 		 * Get time after operating.
358 		 */
359 		ret = get_file_time(tfile, timetest_table[i].type, &t2);
360 		if (ret != 0) {
361 			(void) fprintf(stderr, "get_file_time(%s %d) = %d\n",
362 			    tfile, timetest_table[i].type, ret);
363 			return (1);
364 		}
365 
366 
367 		/*
368 		 * Ideally, time change would be exactly two seconds, but allow
369 		 * a little slack in case of scheduling delays or similar.
370 		 */
371 		long delta = (long)t2 - (long)t1;
372 		if (delta < 2 || delta > 4) {
373 			(void) fprintf(stderr,
374 			    "%s: BAD time change: t1(%ld), t2(%ld)\n",
375 			    timetest_table[i].name, (long)t1, (long)t2);
376 			return (1);
377 		} else {
378 			(void) fprintf(stderr,
379 			    "%s: good time change: t1(%ld), t2(%ld)\n",
380 			    timetest_table[i].name, (long)t1, (long)t2);
381 		}
382 	}
383 
384 	return (0);
385 }
386