Apply filters to empty directories during scan and match subdirectories in directory rules

This commit is contained in:
Andrea Mazzoleni 2026-09-28 11:24:59 +02:00
parent 11b17d7d04
commit 7decc78c97
3 changed files with 61 additions and 25 deletions

View file

@ -240,25 +240,26 @@ static int filter_apply(struct snapraid_filter* filter, struct snapraid_filter**
*
* This is done allowing a partial matching as far it ends at a directory separator
*/
int match_sub = 0;
if (filter->is_dir && !is_dir)
match_sub = 1;
int match_sub = filter->is_dir;
int ret = 0;
if (filter->is_abs) {
/* preserve the skipped slash because it gives an initial ** its recursive meaning */
if (wnmatch_sub_prev(filter->pattern + 1, path, match_sub, '/') == 0)
if (wnmatch_sub_prev(filter->pattern + 1, path, match_sub, '/') == 0
|| (is_dir && wnmatch_sub_prev(filter->pattern + 1, path, 0, '/') == 0))
ret = filter->direction;
} else {
/* the path is relative, first try to match from the root */
if (wnmatch_sub(filter->pattern, path, match_sub) == 0) {
if (wnmatch_sub(filter->pattern, path, match_sub) == 0
|| (is_dir && wnmatch_sub(filter->pattern, path, 0) == 0)) {
ret = filter->direction;
} else {
/* then try to match after all the / presents */
const char* slash = strchr(path, '/');
while (slash) {
if (wnmatch_sub(filter->pattern, slash + 1, match_sub) == 0) {
if (wnmatch_sub(filter->pattern, slash + 1, match_sub) == 0
|| (is_dir && wnmatch_sub(filter->pattern, slash + 1, 0) == 0)) {
ret = filter->direction;
break;
}

View file

@ -1757,14 +1757,19 @@ static int scan_sub(struct snapraid_scan* scan, int level, int is_diff, char* pa
/* recurse */
pathslash(path_next, PATH_MAX);
pathslash(sub_next, PATH_MAX);
if (scan_sub(scan, level + 1, is_diff, path_next, sub_next, tmp) == 0) {
if (scan_sub(scan, level + 1, is_diff, path_next, sub_next, tmp) != 0) {
processed = 1;
} else {
/* restore removing additions */
pathcatl(sub_next, sub_len, PATH_MAX, name);
/* scan the directory as empty dir */
scan_emptydir(scan, sub_next);
/* scan the directory as empty dir only if selected */
if (filter_emptydir(&state->filterlist, &reason, disk->name, sub_next) == 0
&& filter_emptydir(&scan->local_filter_list, &reason, disk->name, sub_next) == 0) {
scan_emptydir(scan, sub_next);
processed = 1;
}
}
/* or we processed something internally, or we have added the empty dir */
processed = 1;
}
} else {
msg_verbose("Excluding directory '%s' for rule '%s'\n", path_next, filter_type(reason, tmp, PATH_MAX));

View file

@ -1466,8 +1466,8 @@ static const struct filter_scenario FILTER_SCENARIOS[] = {
{ "d1", "file.tmp", -1, 0, 0 },
{ "d1", "sub/file.tmp", -1, 0, 0 },
{ "d1", "cache", 0, -1, -1 },
{ "d1", "cache/data.bin", -1, 0, 0 },
{ "d1", "sub/tmp/data.bin", -1, 0, 0 },
{ "d1", "cache/data.bin", -1, -1, -1 },
{ "d1", "sub/tmp/data.bin", -1, -1, -1 },
{ "d1", "sub/tmp", 0, -1, -1 },
{ "d1", "doc.pdf", 0, 0, 0 },
{ "d1", "other/doc.pdf", 0, 0, 0 },
@ -1485,7 +1485,7 @@ static const struct filter_scenario FILTER_SCENARIOS[] = {
{
{ "d1", "song.mp3", 0, 0, -1 },
{ "d1", "audio/song.mp3", 0, 0, -1 },
{ "d1", "music/track.flac", 0, 0, -1 },
{ "d1", "music/track.flac", 0, 0, 0 },
{ "d1", "music", -1, 0, 0 },
{ "d1", "photo.jpg", -1, 0, -1 },
{ "d1", "photos/photo.jpg", -1, 0, -1 },
@ -1493,6 +1493,36 @@ static const struct filter_scenario FILTER_SCENARIOS[] = {
{ 0 }
}
},
{
"directory rules include descendant directories",
{
{ 1, 0, "", "/music/" },
{ 0 }
},
{
{ "d1", "music", -1, 0, 0 },
{ "d1", "music/live", 0, 0, 0 },
{ "d1", "music/live/empty", 0, 0, 0 },
{ "d1", "other", -1, 0, -1 },
{ "d1", "other/empty", -1, 0, -1 },
{ 0 }
}
},
{
"directory rules exclude descendant directories",
{
{ -1, 0, "", "/cache/" },
{ 0 }
},
{
{ "d1", "cache", 0, -1, -1 },
{ "d1", "cache/sub", -1, -1, -1 },
{ "d1", "cache/sub/empty", -1, -1, -1 },
{ "d1", "other", 0, 0, 0 },
{ "d1", "other/empty", 0, 0, 0 },
{ 0 }
}
},
{
"directory exclude prunes scanner before file include can match",
{
@ -1502,8 +1532,8 @@ static const struct filter_scenario FILTER_SCENARIOS[] = {
},
{
{ "d1", "keep", 0, -1, -1 },
{ "d1", "keep/file.dat", 0, 0, 0 },
{ "d1", "keep/other.dat", -1, 0, 0 },
{ "d1", "keep/file.dat", 0, -1, -1 },
{ "d1", "keep/other.dat", -1, -1, -1 },
{ "d1", "other/file.txt", 0, 0, 0 },
{ 0 }
}
@ -1561,7 +1591,7 @@ static const struct filter_scenario FILTER_SCENARIOS[] = {
{
{ "d1", "cache.tmp", -1, 0, -1 },
{ "d1", "media", -1, 0, 0 },
{ "d1", "media/movie.mkv", 0, 0, -1 },
{ "d1", "media/movie.mkv", 0, 0, 0 },
{ "d1", "docs/readme.txt", -1, 0, -1 },
{ 0 }
}
@ -1575,7 +1605,7 @@ static const struct filter_scenario FILTER_SCENARIOS[] = {
},
{
{ "d1", "logs", 0, -1, -1 },
{ "d1", "logs/a.txt", -1, 0, 0 },
{ "d1", "logs/a.txt", -1, -1, -1 },
{ "d1", "report", -1, 0, 0 },
{ "d1", "report/a.txt", 0, 0, 0 },
{ 0 }
@ -1590,12 +1620,12 @@ static const struct filter_scenario FILTER_SCENARIOS[] = {
},
{
{ "d1", "abs_tmp", 0, -1, -1 },
{ "d1", "abs_tmp/file.txt", -1, 0, 0 },
{ "d1", "abs_tmp/file.txt", -1, -1, -1 },
{ "d1", "sub/abs_tmp/file.txt", 0, 0, 0 },
{ "d1", "rel_tmp", 0, -1, -1 },
{ "d1", "rel_tmp/file.txt", -1, 0, 0 },
{ "d1", "rel_tmp/file.txt", -1, -1, -1 },
{ "d1", "sub/rel_tmp", 0, -1, -1 },
{ "d1", "sub/rel_tmp/file.txt", -1, 0, 0 },
{ "d1", "sub/rel_tmp/file.txt", -1, -1, -1 },
{ 0 }
}
},
@ -1634,7 +1664,7 @@ static const struct filter_scenario FILTER_SCENARIOS[] = {
},
{
{ "d1", "projects/sub/build", 0, -1, -1 },
{ "d1", "projects/sub/build/output.o", -1, 0, 0 },
{ "d1", "projects/sub/build/output.o", -1, -1, -1 },
{ "d1", "projects/sub/nested/build", 0, 0, 0 },
{ "d1", "projects/sub/nested/build/output.o", 0, 0, 0 },
{ "d1", "projects/submarine/build", 0, 0, 0 },
@ -1654,8 +1684,8 @@ static const struct filter_scenario FILTER_SCENARIOS[] = {
{ "d1", "src/components/ui/button.js", 0, 0, -1 },
{ "d1", "src/test", -1, -1, -1 },
{ "d1", "src/components/test", -1, -1, -1 },
{ "d1", "src/test/app.js", -1, 0, -1 },
{ "d1", "src/components/test/app.js", -1, 0, -1 },
{ "d1", "src/test/app.js", -1, -1, -1 },
{ "d1", "src/components/test/app.js", -1, -1, -1 },
{ "d1", "src/app.css", -1, 0, -1 },
{ 0 }
}
@ -1741,7 +1771,7 @@ static const struct filter_scenario FILTER_SCENARIOS[] = {
{
{ "d1", "foo", 0, -1, -1 },
{ "d1", "a/b/foo", 0, -1, -1 },
{ "d1", "a/b/foo/file", -1, 0, 0 },
{ "d1", "a/b/foo/file", -1, -1, -1 },
{ "d1", "a/b/bar", 0, 0, 0 },
{ 0 }
}