summaryrefslogtreecommitdiff
path: root/coreutils/touch.c
blob: dff68cb0046da29482507558de07f4bf195e8979 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
/* vi: set sw=4 ts=4: */
/*
 * Mini touch implementation for busybox
 *
 * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
 *
 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
 */
/* Mar 16, 2003      Manuel Novoa III   (mjn3@codepoet.org)
 *
 * Previous version called open() and then utime().  While this will be
 * be necessary to implement -r and -t, it currently only makes things bigger.
 * Also, exiting on a failure was a bug.  All args should be processed.
 */
//config:config TOUCH
//config:	bool "touch (5.9 kb)"
//config:	default y
//config:	help
//config:	touch is used to create or change the access and/or
//config:	modification timestamp of specified files.
//config:
//config:config FEATURE_TOUCH_NODEREF
//config:	bool "Add support for -h"
//config:	default y
//config:	depends on TOUCH
//config:	help
//config:	Enable touch to have the -h option.
//config:	This requires libc support for lutimes() function.
//config:
//config:config FEATURE_TOUCH_SUSV3
//config:	bool "Add support for SUSV3 features (-a -d -m -t -r)"
//config:	default y
//config:	depends on TOUCH
//config:	help
//config:	Enable touch to use a reference file or a given date/time argument.

//applet:IF_TOUCH(APPLET_NOFORK(touch, touch, BB_DIR_BIN, BB_SUID_DROP, touch))

//kbuild:lib-$(CONFIG_TOUCH) += touch.o

//usage:#define touch_trivial_usage
//usage:       "[-c" IF_FEATURE_TOUCH_SUSV3("am") "]"
//usage:       IF_FEATURE_TOUCH_SUSV3(" [-d DATE] [-t DATE] [-r FILE]")
//usage:       " FILE..."
//usage:#define touch_full_usage "\n\n"
//usage:       "Update the last-modified date on the given FILE[s]\n"
//usage:     "\n	-c	Don't create files"
//usage:	IF_FEATURE_TOUCH_NODEREF(
//usage:     "\n	-h	Don't follow links"
//usage:	)
//usage:	IF_FEATURE_TOUCH_SUSV3(
//usage:     "\n	-a	Change only atime"
//usage:     "\n	-m	Change only mtime"
//usage:     "\n	-d DT	Date/time to use"
//usage:     "\n	-t DT	Date/time to use"
//usage:     "\n	-r FILE	Use FILE's date/time"
//usage:	)
//usage:
//usage:#define touch_example_usage
//usage:       "$ ls -l /tmp/foo\n"
//usage:       "/bin/ls: /tmp/foo: No such file or directory\n"
//usage:       "$ touch /tmp/foo\n"
//usage:       "$ ls -l /tmp/foo\n"
//usage:       "-rw-rw-r--    1 andersen andersen        0 Apr 15 01:11 /tmp/foo\n"

/* coreutils implements:
 * -a   change only the access time
 * -c, --no-create
 *      do not create any files
 * -d, --date=STRING
 *      parse STRING and use it instead of current time
 * -f   (ignored, BSD compat)
 * -m   change only the modification time
 * -h, --no-dereference
 * -r, --reference=FILE
 *      use this file's times instead of current time
 * -t STAMP
 *      use [[CC]YY]MMDDhhmm[.ss] instead of current time
 * --time=WORD
 *      change the specified time: WORD is access, atime, or use
 */

#include "libbb.h"

int touch_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
int touch_main(int argc UNUSED_PARAM, char **argv)
{
	int fd;
	int status = EXIT_SUCCESS;
	int opts;
	enum {
		OPT_c = (1 << 0),
		OPT_r = (1 << 1) * ENABLE_FEATURE_TOUCH_SUSV3,
		OPT_d = (1 << 2) * ENABLE_FEATURE_TOUCH_SUSV3,
		OPT_t = (1 << 3) * ENABLE_FEATURE_TOUCH_SUSV3,
		OPT_a = (1 << 4) * ENABLE_FEATURE_TOUCH_SUSV3,
		OPT_m = (1 << 5) * ENABLE_FEATURE_TOUCH_SUSV3,
		OPT_h = (1 << 6) * ENABLE_FEATURE_TOUCH_NODEREF,
	};
#if ENABLE_FEATURE_TOUCH_SUSV3
	/* NULL = use current time */
	const struct timeval *newtime = NULL;
# if ENABLE_LONG_OPTS
	static const char touch_longopts[] ALIGN1 =
		/* name, has_arg, val */
		"no-create\0"         No_argument       "c"
		"reference\0"         Required_argument "r"
		"date\0"              Required_argument "d"
		IF_FEATURE_TOUCH_NODEREF("no-dereference\0" No_argument "h")
	;
#  define GETOPT32 getopt32long
#  define LONGOPTS ,touch_longopts
# else
#  define GETOPT32 getopt32
#  define LONGOPTS
# endif
	char *reference_file = NULL;
	char *date_str = NULL;
	/* timebuf[0] is atime, timebuf[1] is mtime */
	struct timeval timebuf[2];
	timebuf[1].tv_usec = timebuf[0].tv_usec = 0;
#else
# define reference_file NULL
# define date_str       NULL
# define timebuf        ((struct timeval*)NULL)
# define GETOPT32 getopt32
# define LONGOPTS
#endif

	/* -d and -t both set time. In coreutils,
	 * accepted data format differs a bit between -d and -t.
	 * We accept the same formats for both */
	opts = GETOPT32(argv, "c" IF_FEATURE_TOUCH_SUSV3("r:d:t:am")
				IF_FEATURE_TOUCH_NODEREF("h")
				/*ignored:*/ "f" IF_NOT_FEATURE_TOUCH_SUSV3("am")
				LONGOPTS
				IF_FEATURE_TOUCH_SUSV3(, &reference_file)
				IF_FEATURE_TOUCH_SUSV3(, &date_str)
				IF_FEATURE_TOUCH_SUSV3(, &date_str)
	);

	argv += optind;
	if (!*argv) {
		bb_show_usage();
	}

	if (reference_file) {
		struct stat stbuf;
		xstat(reference_file, &stbuf);
		timebuf[1].tv_sec = timebuf[0].tv_sec = stbuf.st_mtime;
		/* Can use .st_mtim.tv_nsec
		 * (or is it .st_mtimensec?? see date.c)
		 * to set microseconds too.
		 */
		newtime = timebuf;
	}

	if (date_str) {
		struct tm tm_time;
		time_t t;

		//memset(&tm_time, 0, sizeof(tm_time));
		/* Better than memset: makes "HH:MM" dates meaningful */
		time(&t);
		localtime_r(&t, &tm_time);
		parse_datestr(date_str, &tm_time);

		/* Correct any day of week and day of year etc. fields */
		tm_time.tm_isdst = -1;  /* Be sure to recheck dst */
		t = validate_tm_time(date_str, &tm_time);

		timebuf[1].tv_sec = timebuf[0].tv_sec = t;
		newtime = timebuf;
	}

	if ((opts & (OPT_a | OPT_m)) && !newtime) {
		time(&timebuf[0].tv_sec);
		timebuf[1].tv_sec = timebuf[0].tv_sec;
		newtime = timebuf;
	}

	do {
		int result;

		if (opts & (OPT_a | OPT_m)) {
			/* Save original times */
			struct stat stbuf;
			if (stat(*argv, &stbuf) == 0) {
				/* As we must set both times, we lose original
				 * file time microseconds.
				 * Can use .st_mtim.tv_nsec
				 * (or is it .st_mtimensec?? see date.c)
				 * to set microseconds too.
				 * Also, utimensat(2) allows to omit one of the
				 * times to be set. But it is SUSv4.
				 */
				if (!(opts & OPT_a))
					timebuf[0].tv_sec = stbuf.st_atime;
				if (!(opts & OPT_m))
					timebuf[1].tv_sec = stbuf.st_mtime;
			}
		}

		result = (ENABLE_FEATURE_TOUCH_NODEREF && (opts & OPT_h) ? lutimes : utimes)(*argv, newtime);

		if (result != 0) {
			if (errno == ENOENT) { /* no such file? */
				if (opts & OPT_c) {
					/* Creation is disabled, so ignore */
					continue;
				}
				/* Try to create the file */
				fd = open(*argv, O_RDWR | O_CREAT, 0666);
				if (fd >= 0) {
					xclose(fd);
					if (reference_file || date_str)
						utimes(*argv, newtime);
					continue;
				}
			}
			status = EXIT_FAILURE;
			bb_simple_perror_msg(*argv);
		}
	} while (*++argv);

	return status;
}