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