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 /** Compute flags for fstatat()/utimensat(). */
41 static int at_flags(const struct args *args) {
42 if (args->flags & NO_FOLLOW) {
43 return AT_SYMLINK_NOFOLLOW;
49 /** Create any parent directories of the given path. */
50 static int mkdirs(const char *path, mode_t mode) {
52 char *dir = xdirname(path);
57 if (strcmp(dir, ".") == 0) {
61 // Optimistically try the immediate parent first
62 if (mkdir(dir, mode) == 0 || errno == EEXIST) {
66 // Create the parents one-at-a-time
67 char *cur = dir + strspn(dir, "/");
69 cur += strcspn(cur, "/");
70 char *next = cur + strspn(cur, "/");
74 if (mkdir(dir, mode) != 0 && errno != EEXIST) {
88 /** Touch one path. */
89 static int xtouch(const struct args *args, const char *path) {
90 int ret = utimensat(AT_FDCWD, path, args->times, at_flags(args));
91 if (ret == 0 || errno != ENOENT) {
95 if (args->flags & NO_CREATE) {
97 } else if (args->flags & CREATE_PARENTS) {
98 if (mkdirs(path, args->pmode) != 0) {
103 size_t len = strlen(path);
104 if (len > 0 && path[len - 1] == '/') {
105 if (mkdir(path, args->dmode) != 0) {
109 return utimensat(AT_FDCWD, path, args->times, at_flags(args));
111 int fd = open(path, O_WRONLY | O_CREAT, args->fmode);
116 if (futimens(fd, args->times) != 0) {
125 int main(int argc, char *argv[]) {
126 mode_t mask = umask(0);
131 { .tv_nsec = UTIME_OMIT },
132 { .tv_nsec = UTIME_OMIT },
134 .fmode = 0666 & ~mask,
135 .dmode = 0777 & ~mask,
136 .pmode = 0777 & ~mask,
139 bool atime = false, mtime = false;
140 const char *darg = NULL;
141 const char *marg = NULL;
142 const char *rarg = NULL;
144 const char *cmd = argc > 0 ? argv[0] : "xtouch";
146 while (c = getopt(argc, argv, ":M:acd:hmpr:t:"), c != -1) {
155 args.flags |= NO_CREATE;
162 args.flags |= NO_FOLLOW;
168 args.flags |= CREATE_PARENTS;
174 fprintf(stderr, "%s: Missing argument to -%c\n", cmd, optopt);
177 fprintf(stderr, "%s: Unrecognized option -%c\n", cmd, optopt);
184 long mode = strtol(marg, &end, 8);
185 // https://github.com/llvm/llvm-project/issues/64946
187 if (*marg && !*end && mode >= 0 && mode < 01000) {
188 args.fmode = args.dmode = mode;
190 fprintf(stderr, "%s: Invalid mode '%s'\n", cmd, marg);
195 struct timespec times[2];
199 if (fstatat(AT_FDCWD, rarg, &buf, at_flags(&args)) != 0) {
200 fprintf(stderr, "%s: '%s': %s\n", cmd, rarg, xstrerror(errno));
203 times[0] = buf.st_atim;
204 times[1] = buf.st_mtim;
206 if (xgetdate(darg, ×[0]) != 0) {
207 fprintf(stderr, "%s: Parsing time '%s' failed: %s\n", cmd, darg, xstrerror(errno));
212 // Don't use UTIME_NOW, so that multiple paths all get the same timestamp
213 if (xgettime(×[0]) != 0) {
214 perror("xgettime()");
220 if (!atime && !mtime) {
225 args.times[0] = times[0];
228 args.times[1] = times[1];
231 if (optind >= argc) {
232 fprintf(stderr, "%s: No files to touch\n", cmd);
236 int ret = EXIT_SUCCESS;
237 for (; optind < argc; ++optind) {
238 const char *path = argv[optind];
239 if (xtouch(&args, path) != 0) {
240 fprintf(stderr, "%s: '%s': %s\n", cmd, path, xstrerror(errno));