todo
[hband-ld-preload-libs.git] / src / recyclix.c
blob7c45b960171c3d58580b883ded9677bd79a1fd61
2 // see flow chart: https://github.com/bAndie91/libyazzy-preload/blob/master/recyclix-flow.svg
4 #include <sys/stat.h>
5 #include <bits/posix1_lim.h>
6 #include <fcntl.h>
7 #include <stdbool.h>
8 #include <stdlib.h>
9 #include <stdio.h>
10 #include <dlfcn.h>
11 #include <unistd.h>
12 #include <string.h>
13 #include <err.h>
14 #include <errno.h>
15 #include <libgen.h>
16 #include <regex.h>
18 #define REGEXP_SZLIMIT "^([0-9]+)([BkMG]?)(-([0-9]+)([BkMG]?))?$"
19 #define NMATCH_SZLIMIT 6
20 #define REGEXP_EXCLUDE "^!(.*)$"
21 #define NMATCH_EXCLUDE 2
23 off_t recyclix_tosize(const char * str, regoff_t num_start, int digits, regoff_t unit);
24 int recyclix_clonepath_recursive(char * p_path, char * p_donor);
27 int unlinkat(int dirfd, const char *pathname, int flags)
29 int (*real_unlinkat)(int dirfd, const char *pathname, int flags) = dlsym(RTLD_NEXT, "unlinkat");
30 int retval;
32 char *recyclers;
33 char *ptr, *tmp, *comma;
34 char recycler[_POSIX_PATH_MAX];
35 char recycled[_POSIX_PATH_MAX];
36 char atdir[_POSIX_PATH_MAX];
37 char fqpath[_POSIX_PATH_MAX];
38 char mp_path[_POSIX_PATH_MAX];
39 struct stat st1;
40 struct stat st2;
41 ino_t recycler_inode;
42 char *txt_unset = "unset RECYCLER environment if you do not care about Recycle bin";
43 off_t sz_min, sz_max;
44 regex_t regex;
45 regmatch_t pmatch[NMATCH_SZLIMIT];
46 int regex_err;
47 bool excluded;
50 recyclers = getenv("RECYCLER");
51 if(recyclers != NULL)
53 retval = fstatat(dirfd, pathname, &st1, (flags & ~AT_REMOVEDIR /* fstatat hates AT_REMOVEDIR */) | AT_SYMLINK_NOFOLLOW /* we are about to move/unlink the symlink itself */);
54 if(retval != 0)
56 warn("%s", pathname);
58 else
60 while(1)
62 ptr = strchrnul(recyclers, ':');
63 strncpy(recycler, recyclers, ptr - recyclers);
64 recycler[ptr - recyclers] = '\0';
66 if(strncmp(recycler, "~/", 2) == 0)
68 tmp = getenv("HOME");
69 if(tmp == NULL)
71 warnx("Could not resolve: %s", recycler);
72 recycler[0] = '\0';
74 else
76 snprintf(recycler, strlen(tmp) + 1 /* slash */ + (ptr - recyclers - 2 /* tilde+slash*/) + 1 /* 0x00 */, "%s/%s", tmp, recyclers + 2);
80 if(strlen(recycler) > 0)
82 /* Found a Recycle Bin */
83 #ifdef DEBUG
84 warnx("RECYCLER %s", recycler);
85 #endif
87 sz_min = 1; // ignore empty files by default
88 sz_max = 0; // no upper size limit by default
89 excluded = false;
91 /* Reading options for this Recycle Bin */
92 comma = strchr(recycler, ',');
93 if(comma != NULL)
95 comma[0] = '\0';
96 comma++;
98 while(comma != NULL && strlen(comma) > 0)
100 /* comma points to the rest of options */
101 tmp = strchr(comma, ',');
102 if(tmp != NULL)
104 tmp[0] = '\0';
105 /* comma now points to a single option */
106 tmp++;
107 /* tmp now points to the next option(s) */
111 regex_err = regcomp(&regex, REGEXP_SZLIMIT, REG_EXTENDED | REG_ICASE);
112 if(regex_err != 0)
114 goto regexp_error;
116 else if(regexec(&regex, comma, NMATCH_SZLIMIT, pmatch, 0) == 0)
118 #ifdef DEBUG
119 int i;
120 char *s;
121 char *e;
122 for(i=0; i<NMATCH_SZLIMIT; i++)
124 if(pmatch[i].rm_so == -1) break;
125 s = comma + pmatch[i].rm_so;
126 e = comma + pmatch[i].rm_eo;
127 if(i == 0) fprintf(stderr, "$& eq ");
128 else fprintf(stderr, "$%d eq ", i);
129 fprintf(stderr, "'%.*s' [%p:%p]\n", (e - s), s, s, e);
131 #endif
132 sz_min = recyclix_tosize(comma, pmatch[1].rm_so, pmatch[1].rm_eo - pmatch[1].rm_so, pmatch[2].rm_so);
133 sz_max = recyclix_tosize(comma, pmatch[4].rm_so, pmatch[4].rm_eo - pmatch[4].rm_so, pmatch[5].rm_so);
134 #ifdef DEBUG
135 warnx("size min %lu, size max %lu.", sz_min, sz_max);
136 #endif
137 if(sz_max != 0 && sz_min > sz_max)
139 warnx("recyclix: Insane size limits: %lu-%lu", sz_min, sz_max);
140 errno = ECANCELED;
141 return -1;
143 goto next_option;
146 regex_err = regcomp(&regex, REGEXP_EXCLUDE, REG_EXTENDED | REG_ICASE);
147 if(regex_err != 0)
149 goto regexp_error;
151 else if(regexec(&regex, comma, NMATCH_EXCLUDE, pmatch, 0) == 0)
153 strncpy(mp_path, comma + pmatch[1].rm_so, pmatch[1].rm_eo - pmatch[1].rm_so + 1); /* mp_path is carrying user defined regex pattern */
154 regex_err = regcomp(&regex, mp_path, REG_EXTENDED);
155 if(regex_err != 0)
157 goto regexp_error;
159 else
161 regex_err = regexec(&regex, pathname, 0, pmatch, 0);
162 #ifdef DEBUG
163 warnx("exclude: '%s' %c~ /%s/", pathname, regex_err == 0 ? '=' : '!', comma+1);
164 #endif
165 excluded = regex_err == 0 ? true : false;
167 goto next_option;
170 regexp_error:
171 regerror(regex_err, &regex, mp_path, sizeof(mp_path)); /* mp_path is carrying the regex error message */
172 warnx("recyclix: Unrecognized option: '%s' (%s)", comma, mp_path);
173 errno = ECANCELED;
174 return -1;
176 next_option:
177 /* Read the next option (or end) */
178 comma = tmp;
181 retval = stat(recycler, &st2);
182 recycler_inode = st2.st_ino;
183 if(retval != 0)
185 warn("stat: %s", recycler);
187 else
189 if(st2.st_dev != st1.st_dev)
191 /* Cross-device condition */
192 #ifdef DEBUG
193 warnx("xdev: %03u,%03u (looking for %03u,%03u)", major(st2.st_dev), minor(st2.st_dev), major(st1.st_dev), minor(st1.st_dev));
194 #endif
196 else if(S_ISREG(st1.st_mode) /* treating directory, symlink, and other non-regular files sizeless */ &&
197 (st1.st_size < sz_min || (sz_max != 0 && st1.st_size > sz_max)))
199 /* File size exceeds limit */
200 #ifdef DEBUG
201 warnx("filesize %lu, min %lu, max %lu (0=infinite)", st1.st_size, sz_min, sz_max);
202 #endif
204 else if(excluded)
206 /* pathname matched to exclude criteria */
207 #ifdef DEBUG
208 warnx("filename matched to exclude criteria");
209 #endif
211 else
213 /* Recycle Bin and the subject file are on the same fs */
214 /* ptr no longer points to the next RECYCLER */
216 if(pathname[0] == '/')
218 /* Absolute pathname given */
219 strcpy(fqpath, pathname);
221 else
223 /* Relative pathname given */
224 if(dirfd == AT_FDCWD)
226 if(getcwd(atdir, _POSIX_PATH_MAX) == NULL)
228 warn("getcwd");
229 retval = -1;
230 break;
233 else
235 sprintf(fqpath, "/proc/self/fd/%d", dirfd);
236 retval = readlink(fqpath, atdir, _POSIX_PATH_MAX);
237 if(retval == -1)
239 warn("readlink: %s", fqpath);
240 break;
242 atdir[retval] = '\0';
245 sprintf(fqpath, "%s/%s", atdir, pathname);
248 strcpy(atdir, fqpath);
249 tmp = dirname(atdir); /* atdir got changed */
250 ptr = realpath(tmp, NULL);
251 if(ptr == NULL)
253 warn("realpath: %s", tmp);
254 free(ptr);
255 break;
257 /* ptr now points to the resolved path name of fqpath's parent directory */
258 strcpy(atdir, fqpath);
259 tmp = (char*)basename(atdir); /* atdir got changed */
260 sprintf(fqpath, "%s/%s", ptr, tmp);
261 free(ptr);
263 retval = 0;
264 strcpy(mp_path, fqpath);
265 while(1)
267 /* Looking for filesystem's root */
268 strcpy(atdir, mp_path);
269 tmp = dirname(atdir); /* dirname will change atdir */
270 if(strcmp(tmp, "//") == 0)
272 tmp[1] = '\0';
274 retval = stat(tmp, &st2);
275 if(retval != 0)
277 warn("stat: %s", tmp);
278 break;
281 if(st2.st_ino == recycler_inode)
283 /* removing from under the Recycler really removes the file */
284 goto real_unlink;
285 break;
288 if(strcmp(mp_path, "/") == 0 && strcmp(tmp, "/") == 0)
290 /* fqpath is on the root fs */
291 break;
293 if(st1.st_dev != st2.st_dev)
295 /* mp_path is the mountpoint */
296 break;
298 /* Check the parent directory */
299 strcpy(mp_path, tmp);
301 if(retval != 0) break;
303 sprintf(recycled, "%s/%s", recycler, fqpath + strlen(mp_path));
304 #ifdef DEBUG
305 warnx("Filesystem root: %s", mp_path);
306 warnx("Recycled file: %s", recycled);
307 #endif
309 /* Copy directory path onto Recycler */
310 strcpy(atdir, recycled);
311 if(S_ISDIR(st1.st_mode))
313 /* Clone the file (actually the directory) to be removed itself */
314 tmp = atdir;
315 ptr = fqpath;
317 else
319 /* Clone the file's parent directory */
320 tmp = dirname(atdir);
321 ptr = dirname(fqpath);
323 retval = recyclix_clonepath_recursive(tmp, ptr);
324 if(retval != 0)
326 warn("mkdir: %s", tmp);
327 break;
330 if(S_ISDIR(st1.st_mode))
332 goto real_unlink;
334 else
336 retval = renameat(dirfd, pathname, 0, recycled);
337 if(retval != 0)
339 warn("renameat");
340 warnx("Failed to drop file to the Recycler, %s.", txt_unset);
344 /* ptr is unusable to continue */
345 break;
347 /* Looking for next Recycler ... */
351 if(ptr[0] == '\0')
353 #ifdef DEBUG
354 warnx("There is no valid Recycler, removing file.");
355 #endif
356 goto real_unlink;
357 break;
359 else
361 recyclers = ptr+1;
366 else
368 real_unlink:
369 retval = real_unlinkat(dirfd, pathname, flags);
372 return retval;
375 int recyclix_clonepath_recursive(char * p_path, char * p_donor)
377 int (*real_unlink)(const char *pathname) = dlsym(RTLD_NEXT, "unlink");
378 int retval;
379 char path[_POSIX_PATH_MAX];
380 char donor[_POSIX_PATH_MAX];
381 char * parent;
382 char * dparent;
383 struct stat st;
384 struct stat donor_st;
386 #ifdef DEBUG
387 warnx("%s: %s", __func__, p_path);
388 #endif
390 /* save stats of directory being cloned */
391 retval = stat(p_donor, &donor_st);
392 if(retval == -1)
394 warn("stat: %s", p_donor);
396 else
398 try_mkdir:
399 retval = mkdir(p_path, donor_st.st_mode);
400 if(retval == -1)
402 if(errno == ENOENT || errno == ENOTDIR)
404 strcpy(path, p_path);
405 strcpy(donor, p_donor);
406 parent = dirname(path);
407 dparent = dirname(donor);
409 retval = real_unlink(parent);
410 if(retval == 0)
412 goto try_mkdir;
414 else if(errno == ENOENT)
416 retval = recyclix_clonepath_recursive(parent, dparent);
417 if(retval == 0)
419 goto try_mkdir;
422 else
424 warn("unlink: %s", parent);
427 else if(errno == EEXIST)
429 retval = stat(p_path, &st);
430 if(retval == -1)
432 warn("stat: %s", p_path);
434 else
436 if(S_ISDIR(st.st_mode))
438 chmod(p_path, donor_st.st_mode);
439 retval = 0;
441 else
443 retval = real_unlink(p_path);
444 if(retval == 0)
446 goto try_mkdir;
448 else
450 warn("unlink: %s", p_path);
455 else
457 warn("mkdir: %s", p_path);
461 if(retval == 0)
463 chown(p_path, donor_st.st_uid, donor_st.st_gid);
465 return retval;
468 off_t recyclix_tosize(const char * str, regoff_t num_start, int digits, regoff_t unit)
470 char number[16];
471 off_t size;
473 if(num_start == -1)
475 return 0;
477 else
479 snprintf(number, 16>digits+1 ? digits+1 : 16, "%s", str+num_start);
480 size = atoi(number);
482 if(str[unit] == 'k' || str[unit] == 'K') size *= 1024;
483 else if(str[unit] == 'm' || str[unit] == 'M') size *= 1024*1024;
484 else if(str[unit] == 'g' || str[unit] == 'G') size *= 1024*1024*1024;
485 return size;
488 int unlink(const char *pathname)
490 return unlinkat(AT_FDCWD, pathname, 0);