2 // see flow chart: https://github.com/bAndie91/libyazzy-preload/blob/master/recyclix-flow.svg
5 #include <bits/posix1_lim.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");
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
];
42 char *txt_unset
= "unset RECYCLER environment if you do not care about Recycle bin";
45 regmatch_t pmatch
[NMATCH_SZLIMIT
];
50 recyclers
= getenv("RECYCLER");
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 */);
62 ptr
= strchrnul(recyclers
, ':');
63 strncpy(recycler
, recyclers
, ptr
- recyclers
);
64 recycler
[ptr
- recyclers
] = '\0';
66 if(strncmp(recycler
, "~/", 2) == 0)
71 warnx("Could not resolve: %s", recycler
);
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 */
84 warnx("RECYCLER %s", recycler
);
87 sz_min
= 1; // ignore empty files by default
88 sz_max
= 0; // no upper size limit by default
91 /* Reading options for this Recycle Bin */
92 comma
= strchr(recycler
, ',');
98 while(comma
!= NULL
&& strlen(comma
) > 0)
100 /* comma points to the rest of options */
101 tmp
= strchr(comma
, ',');
105 /* comma now points to a single option */
107 /* tmp now points to the next option(s) */
111 regex_err
= regcomp(®ex
, REGEXP_SZLIMIT
, REG_EXTENDED
| REG_ICASE
);
116 else if(regexec(®ex
, comma
, NMATCH_SZLIMIT
, pmatch
, 0) == 0)
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
);
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
);
135 warnx("size min %lu, size max %lu.", sz_min
, sz_max
);
137 if(sz_max
!= 0 && sz_min
> sz_max
)
139 warnx("recyclix: Insane size limits: %lu-%lu", sz_min
, sz_max
);
146 regex_err
= regcomp(®ex
, REGEXP_EXCLUDE
, REG_EXTENDED
| REG_ICASE
);
151 else if(regexec(®ex
, 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(®ex
, mp_path
, REG_EXTENDED
);
161 regex_err
= regexec(®ex
, pathname
, 0, pmatch
, 0);
163 warnx("exclude: '%s' %c~ /%s/", pathname
, regex_err
== 0 ? '=' : '!', comma
+1);
165 excluded
= regex_err
== 0 ? true : false;
171 regerror(regex_err
, ®ex
, mp_path
, sizeof(mp_path
)); /* mp_path is carrying the regex error message */
172 warnx("recyclix: Unrecognized option: '%s' (%s)", comma
, mp_path
);
177 /* Read the next option (or end) */
181 retval
= stat(recycler
, &st2
);
182 recycler_inode
= st2
.st_ino
;
185 warn("stat: %s", recycler
);
189 if(st2
.st_dev
!= st1
.st_dev
)
191 /* Cross-device condition */
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
));
196 else if(st1
.st_size
< sz_min
|| (sz_max
!= 0 && st1
.st_size
> sz_max
))
198 /* File size exceeds limit */
200 warnx("filesize %lu, min %lu, max %lu (0=infinite)", st1
.st_size
, sz_min
, sz_max
);
205 /* pathname matched to exclude criteria */
207 warnx("filename matched to exclude criteria");
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
);
222 /* Relative pathname given */
223 if(dirfd
== AT_FDCWD
)
225 if(getcwd(atdir
, _POSIX_PATH_MAX
) == NULL
)
234 sprintf(fqpath
, "/proc/self/fd/%d", dirfd
);
235 retval
= readlink(fqpath
, atdir
, _POSIX_PATH_MAX
);
238 warn("readlink: %s", fqpath
);
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
);
252 warn("realpath: %s", tmp
);
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
);
263 strcpy(mp_path
, fqpath
);
266 /* Looking for filesystem's root */
267 strcpy(atdir
, mp_path
);
268 tmp
= dirname(atdir
); /* dirname will change atdir */
269 if(strcmp(tmp
, "//") == 0)
273 retval
= stat(tmp
, &st2
);
276 warn("stat: %s", tmp
);
280 if(st2
.st_ino
== recycler_inode
)
282 /* removing from under the Recycler really removes the file */
287 if(strcmp(mp_path
, "/") == 0 && strcmp(tmp
, "/") == 0)
289 /* fqpath is on the root fs */
292 if(st1
.st_dev
!= st2
.st_dev
)
294 /* mp_path is the mountpoint */
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
));
304 warnx("Filesystem root: %s", mp_path
);
305 warnx("Recycled file: %s", recycled
);
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 */
318 /* Clone the file's parent directory */
319 tmp
= dirname(atdir
);
320 ptr
= dirname(fqpath
);
322 retval
= recyclix_clonepath_recursive(tmp
, ptr
);
325 warn("mkdir: %s", tmp
);
329 if(S_ISDIR(st1
.st_mode
))
335 retval
= renameat(dirfd
, pathname
, 0, recycled
);
339 warnx("Failed to drop file to the Recycler, %s.", txt_unset
);
343 /* ptr is unusable to continue */
346 /* Looking for next Recycler ... */
353 warnx("There is no valid Recycler, removing file.");
368 retval
= real_unlinkat(dirfd
, pathname
, flags
);
374 int recyclix_clonepath_recursive(char * p_path
, char * p_donor
)
376 int (*real_unlink
)(const char *pathname
) = dlsym(RTLD_NEXT
, "unlink");
378 char path
[_POSIX_PATH_MAX
];
379 char donor
[_POSIX_PATH_MAX
];
383 struct stat donor_st
;
386 warnx("%s: %s", __func__
, p_path
);
389 /* save stats of directory being cloned */
390 retval
= stat(p_donor
, &donor_st
);
393 warn("stat: %s", p_donor
);
398 retval
= mkdir(p_path
, donor_st
.st_mode
);
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
);
413 else if(errno
== ENOENT
)
415 retval
= recyclix_clonepath_recursive(parent
, dparent
);
423 warn("unlink: %s", parent
);
426 else if(errno
== EEXIST
)
428 retval
= stat(p_path
, &st
);
431 warn("stat: %s", p_path
);
435 if(S_ISDIR(st
.st_mode
))
437 chmod(p_path
, donor_st
.st_mode
);
442 retval
= real_unlink(p_path
);
449 warn("unlink: %s", p_path
);
456 warn("mkdir: %s", p_path
);
462 chown(p_path
, donor_st
.st_uid
, donor_st
.st_gid
);
467 off_t
recyclix_tosize(const char * str
, regoff_t num_start
, int digits
, regoff_t unit
)
478 snprintf(number
, 16>digits
+1 ? digits
+1 : 16, "%s", str
+num_start
);
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;
487 int unlink(const char *pathname
)
489 return unlinkat(AT_FDCWD
, pathname
, 0);