1 // Copyright © Tavian Barnes <tavianator@tavianator.com>
2 // SPDX-License-Identifier: 0BSD
4 #include "../src/bfstd.h"
5 #include "../src/config.h"
6 #include "../src/sanity.h"
7 #include "../src/xtime.h"
17 /** Parsed xtouch arguments. */
21 /** Don't create nonexistent files (-c). */
23 /** Don't follow symlinks (-h). */
25 /** Create any missing parent directories (-p). */
26 CREATE_PARENTS = 1 << 2,
29 /** Timestamps (-r|-t|-d). */
30 struct timespec times[2];
32 /** File creation mode (-M; default 0666 & ~umask). */
34 /** Directory creation mode (-M; default 0777 & ~umask). */
36 /** Parent directory creation mode (0777 & ~umask). */
40 /** Open (and maybe create) a single directory. */
41 static int open_dir(const struct args *args, int dfd, const char *path) {
42 int ret = openat(dfd, path, O_SEARCH | O_DIRECTORY);
44 if (ret < 0 && errno == ENOENT && (args->flags & CREATE_PARENTS)) {
45 if (mkdirat(dfd, path, args->pmode) == 0 || errno == EEXIST) {
46 ret = openat(dfd, path, O_SEARCH | O_DIRECTORY);
53 /** Open (and maybe create) the parent directory of the path. */
54 static int open_parent(const struct args *args, const char **path) {
55 size_t max = xbaseoff(*path);
60 char *dir = strndup(*path, max);
65 // Optimistically try the whole path first
66 int dfd = open_dir(args, AT_FDCWD, dir);
75 if (args->flags & CREATE_PARENTS) {
84 // Open the parents one-at-a-time
89 next += strcspn(next, "/");
90 next += strspn(next, "/");
96 dfd = open_dir(args, parent, cur);
98 close_quietly(parent);
115 /** Compute flags for fstatat()/utimensat(). */
116 static int at_flags(const struct args *args) {
117 if (args->flags & NO_FOLLOW) {
118 return AT_SYMLINK_NOFOLLOW;
124 /** Touch one path. */
125 static int xtouch(const struct args *args, const char *path) {
126 int dfd = open_parent(args, &path);
127 if (dfd < 0 && dfd != AT_FDCWD) {
131 int ret = utimensat(dfd, path, args->times, at_flags(args));
132 if (ret == 0 || errno != ENOENT) {
136 if (args->flags & NO_CREATE) {
141 size_t len = strlen(path);
142 if (len > 0 && path[len - 1] == '/') {
143 if (mkdirat(dfd, path, args->dmode) == 0) {
144 ret = utimensat(dfd, path, args->times, at_flags(args));
147 int fd = openat(dfd, path, O_WRONLY | O_CREAT, args->fmode);
149 if (futimens(fd, args->times) == 0) {
164 int main(int argc, char *argv[]) {
165 mode_t mask = umask(0);
170 { .tv_nsec = UTIME_OMIT },
171 { .tv_nsec = UTIME_OMIT },
173 .fmode = 0666 & ~mask,
174 .dmode = 0777 & ~mask,
175 .pmode = 0777 & ~mask,
178 bool atime = false, mtime = false;
179 const char *darg = NULL;
180 const char *marg = NULL;
181 const char *rarg = NULL;
183 const char *cmd = argc > 0 ? argv[0] : "xtouch";
185 while (c = getopt(argc, argv, ":M:acd:hmpr:t:"), c != -1) {
194 args.flags |= NO_CREATE;
201 args.flags |= NO_FOLLOW;
207 args.flags |= CREATE_PARENTS;
213 fprintf(stderr, "%s: Missing argument to -%c\n", cmd, optopt);
216 fprintf(stderr, "%s: Unrecognized option -%c\n", cmd, optopt);
223 long mode = strtol(marg, &end, 8);
224 // https://github.com/llvm/llvm-project/issues/64946
226 if (*marg && !*end && mode >= 0 && mode < 01000) {
227 args.fmode = args.dmode = mode;
229 fprintf(stderr, "%s: Invalid mode '%s'\n", cmd, marg);
234 struct timespec times[2];
238 if (fstatat(AT_FDCWD, rarg, &buf, at_flags(&args)) != 0) {
239 fprintf(stderr, "%s: '%s': %s\n", cmd, rarg, xstrerror(errno));
242 times[0] = buf.st_atim;
243 times[1] = buf.st_mtim;
245 if (xgetdate(darg, ×[0]) != 0) {
246 fprintf(stderr, "%s: Parsing time '%s' failed: %s\n", cmd, darg, xstrerror(errno));
251 // Don't use UTIME_NOW, so that multiple paths all get the same timestamp
252 if (xgettime(×[0]) != 0) {
253 perror("xgettime()");
259 if (!atime && !mtime) {
264 args.times[0] = times[0];
267 args.times[1] = times[1];
270 if (optind >= argc) {
271 fprintf(stderr, "%s: No files to touch\n", cmd);
275 int ret = EXIT_SUCCESS;
276 for (; optind < argc; ++optind) {
277 const char *path = argv[optind];
278 if (xtouch(&args, path) != 0) {
279 fprintf(stderr, "%s: '%s': %s\n", cmd, path, xstrerror(errno));