Skip to content

Commit b8b0d8d

Browse files
author
Will
committed
watcher/linux: fix and test for issue 73, non-assoc rename events
1 parent c3d4a0a commit b8b0d8d

6 files changed

Lines changed: 88 additions & 20 deletions

File tree

changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
- **PR#76, jordan-woyak**
99
Linux: Fix `epoll_wait` failing with `EINTR`.
1010
`epoll_wait` can spontaneously fail with `EINTR`.
11+
- Fixed Issue 73 (Rename events are occasionally split into two events on linux).
1112

1213
## 0.13.5
1314
- C++17 compatibility fixes on Linux (avoid captured structured bindings).

devel/include/detail/wtr/watcher/adapter/linux/fanotify/watch.hpp

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ pathof(fanotify_event_metadata const* const mtd, int* ec) -> std::string
149149
/* Directory name */
150150
int fd = open_by_handle_at(AT_FDCWD, dir_fh, ofl);
151151
if (fd <= 0) {
152-
*ec = errno;
152+
*ec = -errno;
153153
return {};
154154
}
155155
char fs_ev_pidpath[32] = {0};
@@ -188,6 +188,9 @@ struct Parsed {
188188
unsigned this_len = 0;
189189
};
190190

191+
// The error code is -errno if there was a system error which prevents
192+
// correctly parsing the event, or a positive error if the event was
193+
// malformed or unexpected (without a system error).
191194
inline auto
192195
parse_ev(fanotify_event_metadata const* const m, size_t read_len, int* ec)
193196
-> Parsed
@@ -213,10 +216,11 @@ parse_ev(fanotify_event_metadata const* const m, size_t read_len, int* ec)
213216
auto e = ev(ev(pathof(m, ec), et, pt), ev(pathof(n, ec), et, pt));
214217
return {e, nn, here_to_nnn};
215218
};
216-
return ! n ? one(m)
219+
return et != ev_et::rename ? one(m)
220+
: ! n ? (*ec = 2, one(m))
217221
: isfromto(m->mask, n->mask) ? assoc(m, n)
218222
: isfromto(n->mask, m->mask) ? assoc(n, m)
219-
: one(m);
223+
: (*ec = 2, one(m));
220224
}
221225

222226
inline auto is_newdir = [](::wtr::watcher::event const& ev) -> bool
@@ -275,13 +279,13 @@ inline auto do_ev_recv = [](auto const& cb, sysres& sr) -> result
275279
else {
276280
int ec = 0;
277281
auto r = parse_ev(mtd, read_len, &ec);
278-
if (ec) return result::w_sys_bad_fd;
282+
if (ec < 0) return result::w_sys_bad_fd;
279283
if (is_newdir(r.ev))
280284
walkdir_do(r.ev.path_name.c_str(), [&](auto dir) {
281285
do_mark(dir, sr.ke.fd, cb);
282286
cb({dir, r.ev.effect_type, r.ev.path_type});
283287
});
284-
else
288+
else if (ec == 0)
285289
cb(r.ev);
286290
mtd = r.next;
287291
read_len -= r.this_len;

devel/include/detail/wtr/watcher/adapter/linux/inotify/watch.hpp

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,8 @@ struct parsed {
187187
inline auto parse_ev = [](
188188
ke_in_ev::paths const& dm,
189189
inotify_event const* const in,
190-
inotify_event const* const tail) -> parsed
190+
inotify_event const* const tail,
191+
int* ec) -> parsed
191192
{
192193
using ev = ::wtr::watcher::event;
193194
using ev_pt = enum ev::path_type;
@@ -212,7 +213,7 @@ inline auto parse_ev = [](
212213
return ! isassoc(in, next) ? one(in, next)
213214
: isfromto(in, next) ? assoc(in, next)
214215
: isfromto(next, in) ? assoc(next, in)
215-
: one(in, next);
216+
: (*ec = 1, one(in, next));
216217
};
217218

218219
struct defer_dm_rm_wd {
@@ -346,13 +347,14 @@ inline auto do_ev_recv = [](auto const& cb, sysres& sr) -> result
346347
else if (msk & IN_Q_OVERFLOW)
347348
send_msg(result::w_sys_q_overflow, "", cb);
348349
else if (is_real_event(msk)) {
349-
auto parsed = parse_ev(sr.ke.dm, in_ev, in_ev_tail);
350+
int ec = 0;
351+
auto parsed = parse_ev(sr.ke.dm, in_ev, in_ev_tail, &ec);
350352
if (msk & IN_ISDIR && msk & IN_CREATE)
351353
walkdir_do(parsed.ev.path_name.c_str(), [&](auto dir) {
352354
do_mark(dir, sr.ke.fd, sr.ke.dm, cb);
353355
cb({dir, parsed.ev.effect_type, parsed.ev.path_type});
354356
});
355-
else
357+
else if (! ec)
356358
cb(parsed.ev);
357359
in_ev_next = parsed.next;
358360
}

devel/include/detail/wtr/watcher/adapter/linux/watch.hpp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,6 @@ inline auto watch =
2929
if (ep_c < 0) {
3030
if (errno != EINTR) sr.ok = result::e_sys_api_epoll;
3131
}
32-
if (ep_c < 0)
33-
sr.ok = result::e_sys_api_epoll;
3432
else
3533
for (int n = 0; n < ep_c; ++n)
3634
if (is_ev_of(n, sr.il.fd))

include/wtr/watcher.hpp

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1135,7 +1135,7 @@ pathof(fanotify_event_metadata const* const mtd, int* ec) -> std::string
11351135
/* Directory name */
11361136
int fd = open_by_handle_at(AT_FDCWD, dir_fh, ofl);
11371137
if (fd <= 0) {
1138-
*ec = errno;
1138+
*ec = -errno;
11391139
return {};
11401140
}
11411141
char fs_ev_pidpath[32] = {0};
@@ -1174,6 +1174,9 @@ struct Parsed {
11741174
unsigned this_len = 0;
11751175
};
11761176

1177+
// The error code is -errno if there was a system error which prevents
1178+
// correctly parsing the event, or a positive error if the event was
1179+
// malformed or unexpected (without a system error).
11771180
inline auto
11781181
parse_ev(fanotify_event_metadata const* const m, size_t read_len, int* ec)
11791182
-> Parsed
@@ -1199,10 +1202,11 @@ parse_ev(fanotify_event_metadata const* const m, size_t read_len, int* ec)
11991202
auto e = ev(ev(pathof(m, ec), et, pt), ev(pathof(n, ec), et, pt));
12001203
return {e, nn, here_to_nnn};
12011204
};
1202-
return ! n ? one(m)
1205+
return et != ev_et::rename ? one(m)
1206+
: ! n ? (*ec = 2, one(m))
12031207
: isfromto(m->mask, n->mask) ? assoc(m, n)
12041208
: isfromto(n->mask, m->mask) ? assoc(n, m)
1205-
: one(m);
1209+
: (*ec = 2, one(m));
12061210
}
12071211

12081212
inline auto is_newdir = [](::wtr::watcher::event const& ev) -> bool
@@ -1261,13 +1265,13 @@ inline auto do_ev_recv = [](auto const& cb, sysres& sr) -> result
12611265
else {
12621266
int ec = 0;
12631267
auto r = parse_ev(mtd, read_len, &ec);
1264-
if (ec) return result::w_sys_bad_fd;
1268+
if (ec < 0) return result::w_sys_bad_fd;
12651269
if (is_newdir(r.ev))
12661270
walkdir_do(r.ev.path_name.c_str(), [&](auto dir) {
12671271
do_mark(dir, sr.ke.fd, cb);
12681272
cb({dir, r.ev.effect_type, r.ev.path_type});
12691273
});
1270-
else
1274+
else if (ec == 0)
12711275
cb(r.ev);
12721276
mtd = r.next;
12731277
read_len -= r.this_len;
@@ -1466,7 +1470,8 @@ struct parsed {
14661470
inline auto parse_ev = [](
14671471
ke_in_ev::paths const& dm,
14681472
inotify_event const* const in,
1469-
inotify_event const* const tail) -> parsed
1473+
inotify_event const* const tail,
1474+
int* ec) -> parsed
14701475
{
14711476
using ev = ::wtr::watcher::event;
14721477
using ev_pt = enum ev::path_type;
@@ -1491,7 +1496,7 @@ inline auto parse_ev = [](
14911496
return ! isassoc(in, next) ? one(in, next)
14921497
: isfromto(in, next) ? assoc(in, next)
14931498
: isfromto(next, in) ? assoc(next, in)
1494-
: one(in, next);
1499+
: (*ec = 1, one(in, next));
14951500
};
14961501

14971502
struct defer_dm_rm_wd {
@@ -1625,13 +1630,14 @@ inline auto do_ev_recv = [](auto const& cb, sysres& sr) -> result
16251630
else if (msk & IN_Q_OVERFLOW)
16261631
send_msg(result::w_sys_q_overflow, "", cb);
16271632
else if (is_real_event(msk)) {
1628-
auto parsed = parse_ev(sr.ke.dm, in_ev, in_ev_tail);
1633+
int ec = 0;
1634+
auto parsed = parse_ev(sr.ke.dm, in_ev, in_ev_tail, &ec);
16291635
if (msk & IN_ISDIR && msk & IN_CREATE)
16301636
walkdir_do(parsed.ev.path_name.c_str(), [&](auto dir) {
16311637
do_mark(dir, sr.ke.fd, sr.ke.dm, cb);
16321638
cb({dir, parsed.ev.effect_type, parsed.ev.path_type});
16331639
});
1634-
else
1640+
else if (! ec)
16351641
cb(parsed.ev);
16361642
in_ev_next = parsed.next;
16371643
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
#! /usr/bin/env bash
2+
# shellcheck source=tool/test/.ctx
3+
4+
ec=0
5+
6+
(
7+
desc='Rename events are associated events'
8+
count=1000
9+
read -r -d '' expect << .
10+
[
11+
{
12+
"effect_type": "create",
13+
"path_name": "s/self/live@d",
14+
"path_type": "watcher"
15+
},
16+
{
17+
"effect_type": "create",
18+
"path_name": "d/f.0",
19+
"path_type": "file"
20+
},
21+
$(for i in $(seq 1 $count); do cat << eol
22+
{
23+
"associated": {
24+
"effect_type": "rename",
25+
"path_name": "d/f.$i",
26+
"path_type": "file"
27+
},
28+
"effect_type": "rename",
29+
"path_name": "d/f.$((i - 1))",
30+
"path_type": "file"
31+
},
32+
eol
33+
done)
34+
{
35+
"effect_type": "destroy",
36+
"path_name": "s/self/die@d",
37+
"path_type": "watcher"
38+
}
39+
]
40+
.
41+
echo -n "$desc ... "
42+
43+
. "$(dirname "$0")/.ctx"
44+
45+
actual=$(
46+
watch-async "$testdir" -s 3 > "$testdir.json"
47+
touch f.0
48+
for i in $(seq 1 $count)
49+
do mv f.$((i - 1)) f.$i
50+
done
51+
wait # for the watcher
52+
show-events "$testdir" | without-effect-time
53+
)
54+
55+
check-result "$expect" "$actual"
56+
)
57+
ec=$((ec + $?))

0 commit comments

Comments
 (0)