]> Sergey Matveev's repositories - bfs.git/blob - tests/xtouch.c
bfstd: Add a thread-safe wrapper for strerror()
[bfs.git] / tests / xtouch.c
1 // Copyright © Tavian Barnes <tavianator@tavianator.com>
2 // SPDX-License-Identifier: 0BSD
3
4 #include "../src/bfstd.h"
5 #include "../src/config.h"
6 #include "../src/sanity.h"
7 #include "../src/xtime.h"
8 #include <errno.h>
9 #include <fcntl.h>
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <string.h>
13 #include <sys/stat.h>
14 #include <time.h>
15 #include <unistd.h>
16
17 /** Parsed xtouch arguments. */
18 struct args {
19         /** Simple flags. */
20         enum {
21                 /** Don't create nonexistent files (-c). */
22                 NO_CREATE = 1 << 0,
23                 /** Don't follow symlinks (-h). */
24                 NO_FOLLOW = 1 << 1,
25                 /** Create any missing parent directories (-p). */
26                 CREATE_PARENTS = 1 << 2,
27         } flags;
28
29         /** Timestamps (-r|-t|-d). */
30         struct timespec times[2];
31
32         /** File creation mode (-M; default 0666 & ~umask). */
33         mode_t fmode;
34         /** Directory creation mode (-M; default 0777 & ~umask). */
35         mode_t dmode;
36         /** Parent directory creation mode (0777 & ~umask). */
37         mode_t pmode;
38 };
39
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;
44         } else {
45                 return 0;
46         }
47 }
48
49 /** Create any parent directories of the given path. */
50 static int mkdirs(const char *path, mode_t mode) {
51         int ret = -1;
52         char *dir = xdirname(path);
53         if (!dir) {
54                 goto err;
55         }
56
57         if (strcmp(dir, ".") == 0) {
58                 goto done;
59         }
60
61         // Optimistically try the immediate parent first
62         if (mkdir(dir, mode) == 0 || errno == EEXIST) {
63                 goto done;
64         }
65
66         // Create the parents one-at-a-time
67         char *cur = dir + strspn(dir, "/");
68         while (*cur) {
69                 cur += strcspn(cur, "/");
70                 char *next = cur + strspn(cur, "/");
71
72                 char c = *cur;
73                 *cur = '\0';
74                 if (mkdir(dir, mode) != 0 && errno != EEXIST) {
75                         goto err;
76                 }
77                 *cur = c;
78                 cur = next;
79         }
80
81 done:
82         ret = 0;
83 err:
84         free(dir);
85         return ret;
86 }
87
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) {
92                 return ret;
93         }
94
95         if (args->flags & NO_CREATE) {
96                 return 0;
97         } else if (args->flags & CREATE_PARENTS) {
98                 if (mkdirs(path, args->pmode) != 0) {
99                         return -1;
100                 }
101         }
102
103         size_t len = strlen(path);
104         if (len > 0 && path[len - 1] == '/') {
105                 if (mkdir(path, args->dmode) != 0) {
106                         return -1;
107                 }
108
109                 return utimensat(AT_FDCWD, path, args->times, at_flags(args));
110         } else {
111                 int fd = open(path, O_WRONLY | O_CREAT, args->fmode);
112                 if (fd < 0) {
113                         return -1;
114                 }
115
116                 if (futimens(fd, args->times) != 0) {
117                         close_quietly(fd);
118                         return -1;
119                 }
120
121                 return xclose(fd);
122         }
123 }
124
125 int main(int argc, char *argv[]) {
126         mode_t mask = umask(0);
127
128         struct args args = {
129                 .flags = 0,
130                 .times = {
131                         { .tv_nsec = UTIME_OMIT },
132                         { .tv_nsec = UTIME_OMIT },
133                 },
134                 .fmode = 0666 & ~mask,
135                 .dmode = 0777 & ~mask,
136                 .pmode = 0777 & ~mask,
137         };
138
139         bool atime = false, mtime = false;
140         const char *darg = NULL;
141         const char *marg = NULL;
142         const char *rarg = NULL;
143
144         const char *cmd = argc > 0 ? argv[0] : "xtouch";
145         int c;
146         while (c = getopt(argc, argv, ":M:acd:hmpr:t:"), c != -1) {
147                 switch (c) {
148                 case 'M':
149                         marg = optarg;
150                         break;
151                 case 'a':
152                         atime = true;
153                         break;
154                 case 'c':
155                         args.flags |= NO_CREATE;
156                         break;
157                 case 'd':
158                 case 't':
159                         darg = optarg;
160                         break;
161                 case 'h':
162                         args.flags |= NO_FOLLOW;
163                         break;
164                 case 'm':
165                         mtime = true;
166                         break;
167                 case 'p':
168                         args.flags |= CREATE_PARENTS;
169                         break;
170                 case 'r':
171                         rarg = optarg;
172                         break;
173                 case ':':
174                         fprintf(stderr, "%s: Missing argument to -%c\n", cmd, optopt);
175                         return EXIT_FAILURE;
176                 case '?':
177                         fprintf(stderr, "%s: Unrecognized option -%c\n", cmd, optopt);
178                         return EXIT_FAILURE;
179                 }
180         }
181
182         if (marg) {
183                 char *end;
184                 long mode = strtol(marg, &end, 8);
185                 // https://github.com/llvm/llvm-project/issues/64946
186                 sanitize_init(&end);
187                 if (*marg && !*end && mode >= 0 && mode < 01000) {
188                         args.fmode = args.dmode = mode;
189                 } else {
190                         fprintf(stderr, "%s: Invalid mode '%s'\n", cmd, marg);
191                         return EXIT_FAILURE;
192                 }
193         }
194
195         struct timespec times[2];
196
197         if (rarg) {
198                 struct stat buf;
199                 if (fstatat(AT_FDCWD, rarg, &buf, at_flags(&args)) != 0) {
200                         fprintf(stderr, "%s: '%s': %s\n", cmd, rarg, xstrerror(errno));
201                         return EXIT_FAILURE;
202                 }
203                 times[0] = buf.st_atim;
204                 times[1] = buf.st_mtim;
205         } else if (darg) {
206                 if (xgetdate(darg, &times[0]) != 0) {
207                         fprintf(stderr, "%s: Parsing time '%s' failed: %s\n", cmd, darg, xstrerror(errno));
208                         return EXIT_FAILURE;
209                 }
210                 times[1] = times[0];
211         } else {
212                 // Don't use UTIME_NOW, so that multiple paths all get the same timestamp
213                 if (xgettime(&times[0]) != 0) {
214                         perror("xgettime()");
215                         return EXIT_FAILURE;
216                 }
217                 times[1] = times[0];
218         }
219
220         if (!atime && !mtime) {
221                 atime = true;
222                 mtime = true;
223         }
224         if (atime) {
225                 args.times[0] = times[0];
226         }
227         if (mtime) {
228                 args.times[1] = times[1];
229         }
230
231         if (optind >= argc) {
232                 fprintf(stderr, "%s: No files to touch\n", cmd);
233                 return EXIT_FAILURE;
234         }
235
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));
241                         ret = EXIT_FAILURE;
242                 }
243         }
244         return ret;
245 }