3 #include <bits/posix1_lim.h>
16 #define REGEXP_SZLIMIT "^([0-9]+)([BkMG]?)(-([0-9]+)([BkMG]?))?$"
17 #define NMATCH_SZLIMIT 6
18 #define REGEXP_EXCLUDE "^!(.*)$"
19 #define NMATCH_EXCLUDE 2
21 off_t
recyclix_tosize(const char * str
, regoff_t num_start
, int digits
, regoff_t unit
);
22 int recyclix_clonepath_recursive(char * p_path
, char * p_donor
);
25 int unlinkat(int dirfd
, const char *pathname
, int flags
)
27 int (*real_unlinkat
)(int dirfd
, const char *pathname
, int flags
) = dlsym(RTLD_NEXT
, "unlinkat");
31 char *ptr
, *tmp
, *comma
;
32 char recycler
[_POSIX_PATH_MAX
];
33 char recycled
[_POSIX_PATH_MAX
];
34 char atdir
[_POSIX_PATH_MAX
];
35 char fqpath
[_POSIX_PATH_MAX
];
36 char mp_path
[_POSIX_PATH_MAX
];
40 char *txt_unset
= "unset RECYCLER environment if you do not care about Recycle bin";
43 regmatch_t pmatch
[NMATCH_SZLIMIT
];
48 recyclers
= getenv("RECYCLER");
51 retval
= fstatat(dirfd
, pathname
, &st1
, (flags
& ~AT_REMOVEDIR
/* fstatat hates AT_REMOVEDIR */) | AT_SYMLINK_NOFOLLOW
/* we are about to move/unlink the symlink itself */);
60 ptr
= strchrnul(recyclers
, ':');
61 strncpy(recycler
, recyclers
, ptr
- recyclers
);
62 recycler
[ptr
- recyclers
] = '\0';
64 if(strncmp(recycler
, "~/", 2) == 0)
69 warnx("Could not resolve: %s", recycler
);
74 snprintf(recycler
, strlen(tmp
) + 1 /* slash */ + (ptr
- recyclers
- 2 /* tilde+slash*/) + 1 /* 0x00 */, "%s/%s", tmp
, recyclers
+ 2);
78 if(strlen(recycler
) > 0)
80 /* Found a Recycle Bin */
82 warnx("RECYCLER %s", recycler
);
85 sz_min
= 1; // ignore empty files by default
86 sz_max
= 0; // no upper size limit by default
89 /* Reading options for this Recycle Bin */
90 comma
= strchr(recycler
, ',');
96 while(comma
!= NULL
&& strlen(comma
) > 0)
98 /* comma points to the rest of options */
99 tmp
= strchr(comma
, ',');
103 /* comma now points to a single option */
105 /* tmp now points to the next option(s) */
109 regex_err
= regcomp(®ex
, REGEXP_SZLIMIT
, REG_EXTENDED
| REG_ICASE
);
114 else if(regexec(®ex
, comma
, NMATCH_SZLIMIT
, pmatch
, 0) == 0)
120 for(i
=0; i
<NMATCH_SZLIMIT
; i
++)
122 if(pmatch
[i
].rm_so
== -1) break;
123 s
= comma
+ pmatch
[i
].rm_so
;
124 e
= comma
+ pmatch
[i
].rm_eo
;
125 if(i
== 0) fprintf(stderr
, "$& eq ");
126 else fprintf(stderr
, "$%d eq ", i
);
127 fprintf(stderr
, "'%.*s' [%p:%p]\n", (e
- s
), s
, s
, e
);
130 sz_min
= recyclix_tosize(comma
, pmatch
[1].rm_so
, pmatch
[1].rm_eo
- pmatch
[1].rm_so
, pmatch
[2].rm_so
);
131 sz_max
= recyclix_tosize(comma
, pmatch
[4].rm_so
, pmatch
[4].rm_eo
- pmatch
[4].rm_so
, pmatch
[5].rm_so
);
133 warnx("size min %lu, size max %lu.", sz_min
, sz_max
);
135 if(sz_max
!= 0 && sz_min
> sz_max
)
137 warnx("recyclix: Insane size limits: %lu-%lu", sz_min
, sz_max
);
144 regex_err
= regcomp(®ex
, REGEXP_EXCLUDE
, REG_EXTENDED
| REG_ICASE
);
149 else if(regexec(®ex
, comma
, NMATCH_EXCLUDE
, pmatch
, 0) == 0)
151 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 */
152 regex_err
= regcomp(®ex
, mp_path
, REG_EXTENDED
);
159 regex_err
= regexec(®ex
, pathname
, 0, pmatch
, 0);
161 warnx("exclude: '%s' %c~ /%s/", pathname
, regex_err
== 0 ? '=' : '!', comma
+1);
163 excluded
= regex_err
== 0 ? true : false;
169 regerror(regex_err
, ®ex
, mp_path
, sizeof(mp_path
)); /* mp_path is carrying the regex error message */
170 warnx("recyclix: Unrecognized option: '%s' (%s)", comma
, mp_path
);
175 /* Read the next option (or end) */
179 retval
= stat(recycler
, &st2
);
180 recycler_inode
= st2
.st_ino
;
183 warn("stat: %s", recycler
);
187 if(st2
.st_dev
!= st1
.st_dev
)
189 /* Cross-device condition */
191 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 else if(st1
.st_size
< sz_min
|| (sz_max
!= 0 && st1
.st_size
> sz_max
))
196 /* File size exceeds limit */
198 warnx("filesize %lu, min %lu, max %lu (0=infinite)", st1
.st_size
, sz_min
, sz_max
);
203 /* pathname matched to exclude criteria */
205 warnx("filename matched to exclude criteria");
210 /* Recycle Bin and the subject file are on the same fs */
211 /* ptr no longer points to the next RECYCLER */
213 if(pathname
[0] == '/')
215 /* Absolute pathname given */
216 strcpy(fqpath
, pathname
);
220 /* Relative pathname given */
221 if(dirfd
== AT_FDCWD
)
223 if(getcwd(atdir
, _POSIX_PATH_MAX
) == NULL
)
232 sprintf(fqpath
, "/proc/self/fd/%d", dirfd
);
233 retval
= readlink(fqpath
, atdir
, _POSIX_PATH_MAX
);
236 warn("readlink: %s", fqpath
);
239 atdir
[retval
] = '\0';
242 sprintf(fqpath
, "%s/%s", atdir
, pathname
);
245 strcpy(atdir
, fqpath
);
246 tmp
= dirname(atdir
); /* atdir got changed */
247 ptr
= realpath(tmp
, NULL
);
250 warn("realpath: %s", tmp
);
254 /* ptr now points to the resolved path name of fqpath's parent directory */
255 strcpy(atdir
, fqpath
);
256 tmp
= (char*)basename(atdir
); /* atdir got changed */
257 sprintf(fqpath
, "%s/%s", ptr
, tmp
);
261 strcpy(mp_path
, fqpath
);
264 /* Looking for filesystem's root */
265 strcpy(atdir
, mp_path
);
266 tmp
= dirname(atdir
); /* dirname will change atdir */
267 if(strcmp(tmp
, "//") == 0)
271 retval
= stat(tmp
, &st2
);
274 warn("stat: %s", tmp
);
278 if(st2
.st_ino
== recycler_inode
)
280 /* removing from under the Recycler really removes the file */
285 if(strcmp(mp_path
, "/") == 0 && strcmp(tmp
, "/") == 0)
287 /* fqpath is on the root fs */
290 if(st1
.st_dev
!= st2
.st_dev
)
292 /* mp_path is the mountpoint */
295 /* Check the parent directory */
296 strcpy(mp_path
, tmp
);
298 if(retval
!= 0) break;
300 sprintf(recycled
, "%s/%s", recycler
, fqpath
+ strlen(mp_path
));
302 warnx("Filesystem root: %s", mp_path
);
303 warnx("Recycled file: %s", recycled
);
306 /* Copy directory path onto Recycler */
307 strcpy(atdir
, recycled
);
308 if(S_ISDIR(st1
.st_mode
))
310 /* Clone the file (actually the directory) to be removed itself */
316 /* Clone the file's parent directory */
317 tmp
= dirname(atdir
);
318 ptr
= dirname(fqpath
);
320 retval
= recyclix_clonepath_recursive(tmp
, ptr
);
323 warn("mkdir: %s", tmp
);
327 if(S_ISDIR(st1
.st_mode
))
333 retval
= renameat(dirfd
, pathname
, 0, recycled
);
337 warnx("Failed to drop file to the Recycler, %s.", txt_unset
);
341 /* ptr is unusable to continue */
344 /* Looking for next Recycler ... */
351 warnx("There is no valid Recycler, removing file.");
366 retval
= real_unlinkat(dirfd
, pathname
, flags
);
372 int recyclix_clonepath_recursive(char * p_path
, char * p_donor
)
374 int (*real_unlink
)(const char *pathname
) = dlsym(RTLD_NEXT
, "unlink");
376 char path
[_POSIX_PATH_MAX
];
377 char donor
[_POSIX_PATH_MAX
];
381 struct stat donor_st
;
384 warnx("%s: %s", __func__
, p_path
);
387 /* save stats of directory being cloned */
388 retval
= stat(p_donor
, &donor_st
);
391 warn("stat: %s", p_donor
);
396 retval
= mkdir(p_path
, donor_st
.st_mode
);
399 if(errno
== ENOENT
|| errno
== ENOTDIR
)
401 strcpy(path
, p_path
);
402 strcpy(donor
, p_donor
);
403 parent
= dirname(path
);
404 dparent
= dirname(donor
);
406 retval
= real_unlink(parent
);
411 else if(errno
== ENOENT
)
413 retval
= recyclix_clonepath_recursive(parent
, dparent
);
421 warn("unlink: %s", parent
);
424 else if(errno
== EEXIST
)
426 retval
= stat(p_path
, &st
);
429 warn("stat: %s", p_path
);
433 if(S_ISDIR(st
.st_mode
))
435 chmod(p_path
, donor_st
.st_mode
);
440 retval
= real_unlink(p_path
);
447 warn("unlink: %s", p_path
);
454 warn("mkdir: %s", p_path
);
460 chown(p_path
, donor_st
.st_uid
, donor_st
.st_gid
);
465 off_t
recyclix_tosize(const char * str
, regoff_t num_start
, int digits
, regoff_t unit
)
476 snprintf(number
, 16>digits
+1 ? digits
+1 : 16, "%s", str
+num_start
);
479 if(str
[unit
] == 'k' || str
[unit
] == 'K') size
*= 1024;
480 else if(str
[unit
] == 'm' || str
[unit
] == 'M') size
*= 1024*1024;
481 else if(str
[unit
] == 'g' || str
[unit
] == 'G') size
*= 1024*1024*1024;
485 int unlink(const char *pathname
)
487 return unlinkat(AT_FDCWD
, pathname
, 0);