Update recyclix-flow.svg
[hband-ld-preload-libs.git] / src / recyclix.c
blob31beddc19747dcdc3a28bef1a908f45f9fb73bef
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(st1.st_size < sz_min || (sz_max != 0 && st1.st_size > sz_max))
198 /* File size exceeds limit */
199 #ifdef DEBUG
200 warnx("filesize %lu, min %lu, max %lu (0=infinite)", st1.st_size, sz_min, sz_max);
201 #endif
203 else if(excluded)
205 /* pathname matched to exclude criteria */
206 #ifdef DEBUG
207 warnx("filename matched to exclude criteria");
208 #endif
210 else
212 /* Recycle Bin and the subject file are on the same fs */
213 /* ptr no longer points to the next RECYCLER */
215 if(pathname[0] == '/')
217 /* Absolute pathname given */
218 strcpy(fqpath, pathname);
220 else
222 /* Relative pathname given */
223 if(dirfd == AT_FDCWD)
225 if(getcwd(atdir, _POSIX_PATH_MAX) == NULL)
227 warn("getcwd");
228 retval = -1;
229 break;
232 else
234 sprintf(fqpath, "/proc/self/fd/%d", dirfd);
235 retval = readlink(fqpath, atdir, _POSIX_PATH_MAX);
236 if(retval == -1)
238 warn("readlink: %s", fqpath);
239 break;
241 atdir[retval] = '\0';
244 sprintf(fqpath, "%s/%s", atdir, pathname);
247 strcpy(atdir, fqpath);
248 tmp = dirname(atdir); /* atdir got changed */
249 ptr = realpath(tmp, NULL);
250 if(ptr == NULL)
252 warn("realpath: %s", tmp);
253 free(ptr);
254 break;
256 /* ptr now points to the resolved path name of fqpath's parent directory */
257 strcpy(atdir, fqpath);
258 tmp = (char*)basename(atdir); /* atdir got changed */
259 sprintf(fqpath, "%s/%s", ptr, tmp);
260 free(ptr);
262 retval = 0;
263 strcpy(mp_path, fqpath);
264 while(1)
266 /* Looking for filesystem's root */
267 strcpy(atdir, mp_path);
268 tmp = dirname(atdir); /* dirname will change atdir */
269 if(strcmp(tmp, "//") == 0)
271 tmp[1] = '\0';
273 retval = stat(tmp, &st2);
274 if(retval != 0)
276 warn("stat: %s", tmp);
277 break;
280 if(st2.st_ino == recycler_inode)
282 /* removing from under the Recycler really removes the file */
283 goto real_unlink;
284 break;
287 if(strcmp(mp_path, "/") == 0 && strcmp(tmp, "/") == 0)
289 /* fqpath is on the root fs */
290 break;
292 if(st1.st_dev != st2.st_dev)
294 /* mp_path is the mountpoint */
295 break;
297 /* Check the parent directory */
298 strcpy(mp_path, tmp);
300 if(retval != 0) break;
302 sprintf(recycled, "%s/%s", recycler, fqpath + strlen(mp_path));
303 #ifdef DEBUG
304 warnx("Filesystem root: %s", mp_path);
305 warnx("Recycled file: %s", recycled);
306 #endif
308 /* Copy directory path onto Recycler */
309 strcpy(atdir, recycled);
310 if(S_ISDIR(st1.st_mode))
312 /* Clone the file (actually the directory) to be removed itself */
313 tmp = atdir;
314 ptr = fqpath;
316 else
318 /* Clone the file's parent directory */
319 tmp = dirname(atdir);
320 ptr = dirname(fqpath);
322 retval = recyclix_clonepath_recursive(tmp, ptr);
323 if(retval != 0)
325 warn("mkdir: %s", tmp);
326 break;
329 if(S_ISDIR(st1.st_mode))
331 goto real_unlink;
333 else
335 retval = renameat(dirfd, pathname, 0, recycled);
336 if(retval != 0)
338 warn("renameat");
339 warnx("Failed to drop file to the Recycler, %s.", txt_unset);
343 /* ptr is unusable to continue */
344 break;
346 /* Looking for next Recycler ... */
350 if(ptr[0] == '\0')
352 #ifdef DEBUG
353 warnx("There is no valid Recycler, removing file.");
354 #endif
355 goto real_unlink;
356 break;
358 else
360 recyclers = ptr+1;
365 else
367 real_unlink:
368 retval = real_unlinkat(dirfd, pathname, flags);
371 return retval;
374 int recyclix_clonepath_recursive(char * p_path, char * p_donor)
376 int (*real_unlink)(const char *pathname) = dlsym(RTLD_NEXT, "unlink");
377 int retval;
378 char path[_POSIX_PATH_MAX];
379 char donor[_POSIX_PATH_MAX];
380 char * parent;
381 char * dparent;
382 struct stat st;
383 struct stat donor_st;
385 #ifdef DEBUG
386 warnx("%s: %s", __func__, p_path);
387 #endif
389 /* save stats of directory being cloned */
390 retval = stat(p_donor, &donor_st);
391 if(retval == -1)
393 warn("stat: %s", p_donor);
395 else
397 try_mkdir:
398 retval = mkdir(p_path, donor_st.st_mode);
399 if(retval == -1)
401 if(errno == ENOENT || errno == ENOTDIR)
403 strcpy(path, p_path);
404 strcpy(donor, p_donor);
405 parent = dirname(path);
406 dparent = dirname(donor);
408 retval = real_unlink(parent);
409 if(retval == 0)
411 goto try_mkdir;
413 else if(errno == ENOENT)
415 retval = recyclix_clonepath_recursive(parent, dparent);
416 if(retval == 0)
418 goto try_mkdir;
421 else
423 warn("unlink: %s", parent);
426 else if(errno == EEXIST)
428 retval = stat(p_path, &st);
429 if(retval == -1)
431 warn("stat: %s", p_path);
433 else
435 if(S_ISDIR(st.st_mode))
437 chmod(p_path, donor_st.st_mode);
438 retval = 0;
440 else
442 retval = real_unlink(p_path);
443 if(retval == 0)
445 goto try_mkdir;
447 else
449 warn("unlink: %s", p_path);
454 else
456 warn("mkdir: %s", p_path);
460 if(retval == 0)
462 chown(p_path, donor_st.st_uid, donor_st.st_gid);
464 return retval;
467 off_t recyclix_tosize(const char * str, regoff_t num_start, int digits, regoff_t unit)
469 char number[16];
470 off_t size;
472 if(num_start == -1)
474 return 0;
476 else
478 snprintf(number, 16>digits+1 ? digits+1 : 16, "%s", str+num_start);
479 size = atoi(number);
481 if(str[unit] == 'k' || str[unit] == 'K') size *= 1024;
482 else if(str[unit] == 'm' || str[unit] == 'M') size *= 1024*1024;
483 else if(str[unit] == 'g' || str[unit] == 'G') size *= 1024*1024*1024;
484 return size;
487 int unlink(const char *pathname)
489 return unlinkat(AT_FDCWD, pathname, 0);