C · 7784 bytes Raw Blame History
1 #include <dirent.h>
2 #include <errno.h>
3 #include <stdint.h>
4 #include <stdio.h>
5 #include <stdlib.h>
6 #include <string.h>
7 #include <sys/stat.h>
8 #include <sys/types.h>
9 #include <unistd.h>
10
11 typedef struct {
12 char *data;
13 size_t len;
14 size_t cap;
15 } fgof_watch_buffer;
16
17 static const char *fgof_watch_basename(const char *path) {
18 const char *slash;
19
20 slash = strrchr(path, '/');
21 if (slash == NULL) {
22 return path;
23 }
24 return slash + 1;
25 }
26
27 static const char *fgof_watch_relative_path(const char *root, const char *path) {
28 size_t root_len;
29
30 if (strcmp(path, root) == 0) {
31 return fgof_watch_basename(root);
32 }
33
34 root_len = strlen(root);
35 if (strncmp(path, root, root_len) == 0 && path[root_len] == '/') {
36 return path + root_len + 1;
37 }
38
39 return path;
40 }
41
42 static int fgof_watch_contains_hidden_segment(const char *path) {
43 const char *segment_start;
44 const char *cursor;
45
46 if (path == NULL || path[0] == '\0') {
47 return 0;
48 }
49
50 segment_start = path;
51 for (cursor = path; ; ++cursor) {
52 if (*cursor != '/' && *cursor != '\0') {
53 continue;
54 }
55 if (cursor > segment_start && segment_start[0] == '.') {
56 return 1;
57 }
58 if (*cursor == '\0') {
59 break;
60 }
61 segment_start = cursor + 1;
62 }
63
64 return 0;
65 }
66
67 static int fgof_watch_matches_prefix(const char *path, int prefix_count, int prefix_stride, const char *prefixes) {
68 int i;
69 const char *prefix;
70 size_t prefix_len;
71
72 if (prefix_count <= 0 || prefix_stride <= 0 || prefixes == NULL) {
73 return 0;
74 }
75
76 for (i = 0; i < prefix_count; ++i) {
77 prefix = prefixes + (i * prefix_stride);
78 prefix_len = strlen(prefix);
79 if (prefix_len == 0) {
80 continue;
81 }
82 if (strcmp(path, prefix) == 0) {
83 return 1;
84 }
85 if (strncmp(path, prefix, prefix_len) == 0 && path[prefix_len] == '/') {
86 return 1;
87 }
88 }
89
90 return 0;
91 }
92
93 static int fgof_watch_should_ignore(
94 const char *root,
95 const char *path,
96 int ignore_hidden,
97 int prefix_count,
98 int prefix_stride,
99 const char *prefixes
100 ) {
101 if (ignore_hidden && fgof_watch_contains_hidden_segment(fgof_watch_relative_path(root, path))) {
102 return 1;
103 }
104
105 if (fgof_watch_matches_prefix(path, prefix_count, prefix_stride, prefixes)) {
106 return 1;
107 }
108
109 return 0;
110 }
111
112 static int fgof_watch_append_text(fgof_watch_buffer *buffer, const char *text, size_t text_len) {
113 char *grown;
114 size_t needed;
115 size_t cap;
116
117 needed = buffer->len + text_len;
118 if (needed <= buffer->cap) {
119 memcpy(buffer->data + buffer->len, text, text_len);
120 buffer->len += text_len;
121 return 0;
122 }
123
124 cap = (buffer->cap == 0) ? 1024 : buffer->cap;
125 while (cap < needed) {
126 cap *= 2;
127 }
128
129 grown = (char *)realloc(buffer->data, cap);
130 if (grown == NULL) {
131 return ENOMEM;
132 }
133
134 buffer->data = grown;
135 buffer->cap = cap;
136 memcpy(buffer->data + buffer->len, text, text_len);
137 buffer->len += text_len;
138 return 0;
139 }
140
141 static int fgof_watch_append_field(fgof_watch_buffer *buffer, const char *text, size_t text_len) {
142 static const char nul = '\0';
143 int status;
144
145 status = fgof_watch_append_text(buffer, text, text_len);
146 if (status != 0) {
147 return status;
148 }
149
150 return fgof_watch_append_text(buffer, &nul, 1);
151 }
152
153 static int fgof_watch_append_int64_field(fgof_watch_buffer *buffer, long long value) {
154 char text[64];
155 int text_len;
156
157 text_len = snprintf(text, sizeof(text), "%lld", value);
158 if (text_len < 0) {
159 return EINVAL;
160 }
161 if ((size_t)text_len >= sizeof(text)) {
162 return EOVERFLOW;
163 }
164
165 return fgof_watch_append_field(buffer, text, (size_t)text_len);
166 }
167
168 static int fgof_watch_append_entry(fgof_watch_buffer *buffer, const char *path, const struct stat *st) {
169 char kind;
170 long long mtime_sec;
171 long long mtime_nsec;
172 int status;
173
174 if (S_ISDIR(st->st_mode)) {
175 kind = 'D';
176 } else {
177 kind = 'F';
178 }
179
180 #if defined(__APPLE__)
181 mtime_sec = (long long)st->st_mtimespec.tv_sec;
182 mtime_nsec = (long long)st->st_mtimespec.tv_nsec;
183 #else
184 mtime_sec = (long long)st->st_mtim.tv_sec;
185 mtime_nsec = (long long)st->st_mtim.tv_nsec;
186 #endif
187
188 status = fgof_watch_append_field(buffer, &kind, 1);
189 if (status != 0) {
190 return status;
191 }
192 status = fgof_watch_append_int64_field(buffer, (long long)st->st_ino);
193 if (status != 0) {
194 return status;
195 }
196 status = fgof_watch_append_int64_field(buffer, (long long)st->st_size);
197 if (status != 0) {
198 return status;
199 }
200 status = fgof_watch_append_int64_field(buffer, mtime_sec);
201 if (status != 0) {
202 return status;
203 }
204 status = fgof_watch_append_int64_field(buffer, mtime_nsec);
205 if (status != 0) {
206 return status;
207 }
208
209 return fgof_watch_append_field(buffer, path, strlen(path));
210 }
211
212 static int fgof_watch_visit(
213 const char *root,
214 const char *path,
215 int recursive,
216 int depth,
217 int ignore_hidden,
218 int prefix_count,
219 int prefix_stride,
220 const char *prefixes,
221 fgof_watch_buffer *buffer
222 ) {
223 DIR *dirp;
224 struct dirent *entry;
225 struct stat st;
226 int status;
227
228 if (fgof_watch_should_ignore(root, path, ignore_hidden, prefix_count, prefix_stride, prefixes)) {
229 return 0;
230 }
231
232 if (lstat(path, &st) != 0) {
233 if (errno == ENOENT || errno == ENOTDIR) {
234 return 0;
235 }
236 return errno;
237 }
238
239 if ((status = fgof_watch_append_entry(buffer, path, &st)) != 0) {
240 return status;
241 }
242
243 if (!S_ISDIR(st.st_mode)) {
244 return 0;
245 }
246
247 if (!recursive && depth > 0) {
248 return 0;
249 }
250
251 dirp = opendir(path);
252 if (dirp == NULL) {
253 if (errno == ENOENT || errno == ENOTDIR) {
254 return 0;
255 }
256 return errno;
257 }
258
259 while ((entry = readdir(dirp)) != NULL) {
260 char *child;
261 size_t child_len;
262 if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
263 continue;
264 }
265
266 child_len = strlen(path) + 1 + strlen(entry->d_name) + 1;
267 child = (char *)malloc(child_len);
268 if (child == NULL) {
269 closedir(dirp);
270 return ENOMEM;
271 }
272
273 snprintf(child, child_len, "%s/%s", path, entry->d_name);
274 status = fgof_watch_visit(
275 root,
276 child,
277 recursive,
278 depth + 1,
279 ignore_hidden,
280 prefix_count,
281 prefix_stride,
282 prefixes,
283 buffer
284 );
285 free(child);
286
287 if (status != 0) {
288 closedir(dirp);
289 return status;
290 }
291 }
292
293 closedir(dirp);
294 return 0;
295 }
296
297 int fgof_watch_collect_snapshot(
298 const char *root,
299 int recursive,
300 int ignore_hidden,
301 int prefix_count,
302 int prefix_stride,
303 const char *prefixes,
304 char **buffer_out,
305 size_t *buffer_len_out
306 ) {
307 fgof_watch_buffer buffer;
308 int status;
309
310 buffer.data = NULL;
311 buffer.len = 0;
312 buffer.cap = 0;
313
314 *buffer_out = NULL;
315 *buffer_len_out = 0;
316
317 if (root == NULL || root[0] == '\0') {
318 return 0;
319 }
320
321 status = fgof_watch_visit(
322 root,
323 root,
324 recursive != 0,
325 0,
326 ignore_hidden != 0,
327 prefix_count,
328 prefix_stride,
329 prefixes,
330 &buffer
331 );
332 if (status != 0) {
333 free(buffer.data);
334 return status;
335 }
336
337 *buffer_out = buffer.data;
338 *buffer_len_out = buffer.len;
339 return 0;
340 }
341
342 void fgof_watch_free_buffer(char *buffer) {
343 free(buffer);
344 }