29 #if !defined(USE_INOTIFY)
33 #if defined(USE_STATPOLL)
37 #if defined(USE_INOTIFY)
38 #include <sys/inotify.h>
46 #if defined(TRACE_INOTIFY)
47 #define IF_TRACE_INOTIFY(x) x
48 #else // !TRACE_INOTIFY
49 #define IF_TRACE_INOTIFY(x)
60 #if defined(USE_INOTIFY)
64 mutable int lastModtime;
65 int getModtime()
const {
67 if (stat(file.c_str(), &st) == 0)
return st.st_mtime;
70 bool was_changed()
const {
71 int currModtime = getModtime();
72 bool changed = currModtime>lastModtime;
73 lastModtime = currModtime;
82 #if defined(USE_INOTIFY)
83 watch_descriptor = -1;
88 #if defined(USE_INOTIFY)
94 const string&
get_name()
const {
return file; }
103 if (!deleted_still_exists) {
105 for (CallbackList::const_iterator
cb = copy.begin();
cb != copy.end(); ++
cb) {
106 if (callbacks.find(*
cb) != callbacks.end()) {
107 (*cb)(file.c_str(), reason);
113 #if defined(USE_INOTIFY)
114 int get_watch_descriptor()
const {
115 return watch_descriptor;
119 watch_descriptor = inotify_add_watch(inotifier, file.c_str(), IN_DELETE_SELF|IN_MOVE_SELF|IN_CLOSE_WRITE|IN_DELETE);
120 return watch_descriptor<0 ?
GB_IO_error(
"watching", file.c_str()) :
NULp;
122 GB_ERROR remove_watch(
int inotifier) {
124 if (watch_descriptor != -1) {
125 if (inotify_rm_watch(inotifier, watch_descriptor)<0) {
128 watch_descriptor = -1;
132 void mark_as_disabled() {
133 watch_descriptor = -1;
137 #else // USE_STATPOLL
145 typedef list<TrackedFilePtr> FileList;
149 #if defined(USE_INOTIFY)
154 SmartCharPtr ievent_buffer;
157 bool reactivate_stale;
159 size_t get_ievent_buffer_size() {
160 return sizeof(inotify_event)+oversize;
162 void realloc_buffer(
int new_oversize) {
164 oversize = new_oversize;
165 ievent_buffer = ARB_alloc<char>(get_ievent_buffer_size());
168 inotify_event *get_ievent_buffer() {
169 if (oversize<0) realloc_buffer(9*
sizeof(inotify_event));
170 return reinterpret_cast<inotify_event*
>(&*ievent_buffer);
172 void increase_buffer_oversize() {
174 realloc_buffer(oversize*3/2);
180 FileList::iterator find(
const string& file) {
181 for (FileList::iterator f = files.begin(); f != files.end(); ++f) {
182 if (file == (*f)->get_name())
return f;
186 #if defined(USE_INOTIFY)
187 FileList::iterator find_watch_descriptor(
int wd) {
188 for (FileList::iterator f = files.begin(); f != files.end(); ++f) {
189 if (wd == (*f)->get_watch_descriptor()) {
196 void check_for_created_files() {
198 for (FileList::iterator fi = files.begin(); fi != files.end(); ++fi) {
199 TrackedFilePtr f = *fi;
200 if (f->get_watch_descriptor() == -1) {
201 GB_ERROR err = f->add_watch(inotifier);
205 #if defined(TRACE_INOTIFY)
206 fprintf(stderr,
"check_for_created_files: add_watch for '%s': %s wd=%i\n", f->get_name().c_str(), err, f->get_watch_descriptor());
215 #if defined(USE_INOTIFY)
219 reactivate_stale(
false)
221 inotifier = inotify_init();
223 error =
GB_IO_error(
"creating",
"<inotify-instance>");
227 for (FileList::iterator fi = files.begin(); fi != files.end(); ++fi) {
228 TrackedFilePtr f = *fi;
229 if (f->get_watch_descriptor() >= 0) {
230 GB_ERROR ferr = f->remove_watch(inotifier);
231 if (ferr) fprintf(stderr,
"Error in ~TrackedFiles: %s\n", ferr);
233 aw_assert(f->get_watch_descriptor() == -1);
240 #if defined(USE_INOTIFY)
244 #else // USE_STATPOLL
250 return files.empty();
252 void insert(
string file,
const FileChangedCallback& fccb) {
255 #if defined(TRACE_INOTIFY_BASIC)
256 fprintf(stderr,
"[inotifier] + %s\n", file.c_str());
259 #if defined(USE_INOTIFY)
262 FileList::iterator foundIter = find(file);
263 TrackedFilePtr found;
265 if (foundIter != files.end()) {
270 files.push_back(found);
271 #if defined(USE_INOTIFY)
272 GB_ERROR werr = found->add_watch(inotifier);
273 if (werr) found->track_creation(
this);
274 #if defined(TRACE_INOTIFY)
275 fprintf(stderr,
"insert: add_watch for '%s': %s wd=%i\n", found->get_name().c_str(), werr, found->get_watch_descriptor());
279 found->add_callback(fccb);
281 void erase(
string file,
const FileChangedCallback& fccb) {
284 #if defined(TRACE_INOTIFY_BASIC)
285 fprintf(stderr,
"[inotifier] - %s\n", file.c_str());
288 #if defined(USE_INOTIFY)
292 FileList::iterator foundIter = find(file);
293 if (foundIter != files.end()) {
294 TrackedFilePtr found = *foundIter;
295 found->remove_callback(fccb);
296 if (found->empty()) {
297 #if defined(USE_INOTIFY)
298 if (found->get_watch_descriptor() != -1) {
299 error = found->remove_watch(inotifier);
302 files.erase(foundIter);
307 void check_changes();
308 #if defined(USE_INOTIFY)
309 void request_reactivate_stale() { reactivate_stale =
true; }
313 #if defined(USE_INOTIFY)
315 #if defined(TRACE_INOTIFY)
316 fprintf(stderr,
"parent_modified_cb(dir='%s', reason=%i) called\n", parent_dir,
int(reason));
318 tracked->request_reactivate_stale();
321 void TrackedFile::track_creation(
TrackedFiles *tracked) {
324 #if defined(TRACE_INOTIFY)
325 fprintf(stderr,
"TrackedFile::track_creation: file='%s'\n", file.c_str());
331 #if defined(TRACE_INOTIFY)
332 fprintf(stderr,
"track_creation: parent_dir='%s'\n", parent_dir);
347 #if defined(USE_INOTIFY)
350 timespec timeout = { 0, 0 };
353 FD_SET(inotifier, &readfds);
356 while ((sel = pselect(inotifier+1, &readfds, NULp, NULp, &timeout, NULp))) {
359 inotify_event *ievent = get_ievent_buffer();
360 ssize_t got = read(inotifier, ievent, get_ievent_buffer_size());
362 if (got >= ssize_t(
sizeof(inotify_event))) {
364 FileList::iterator foundIter = find_watch_descriptor(ievent->wd);
365 if (foundIter != files.end()) {
366 TrackedFilePtr found = *foundIter;
367 bool watch_removed = (ievent->mask & IN_IGNORED);
368 #if defined(TRACE_INOTIFY)
369 const char *name = (ievent->len>1) ? ievent->name :
"";
372 "got inotify event for '%s' (wd=%i, mask=%X, watch_removed=%i) '%s'\n",
373 found->get_name().c_str(), ievent->wd, ievent->mask,
int(watch_removed), name);
376 found->mark_as_disabled();
377 found->track_creation(
this);
379 else if (ievent->mask & IN_DELETE_SELF) {
382 else if (ievent->mask & IN_MOVE_SELF) {
384 found->remove_watch(inotifier);
385 found->track_creation(
this);
386 reactivate_stale =
true;
388 else if (ievent->mask & (IN_DELETE|IN_CLOSE_WRITE)) {
396 #if defined(TRACE_INOTIFY)
397 fprintf(stderr,
"ignoring event for unknown watch_descriptor=%i\n", ievent->wd);
401 int event_len =
sizeof(inotify_event)+ievent->len;
403 ievent = reinterpret_cast<inotify_event*>(reinterpret_cast<char*>(ievent)+event_len);
407 bool buffer_too_small =
false;
411 if (errno == EINVAL) buffer_too_small =
true;
412 else read_err =
GB_IO_error(
"reading",
"<inotify-descriptor>");
414 else if (got == 0) buffer_too_small =
true;
420 fprintf(stderr,
"inotifier broken: %s\n", read_err);
425 if (!buffer_too_small) {
427 GBK_terminate(
"inotify event queue broken? (neither buffer_too_small nor read_err)");
429 increase_buffer_oversize();
432 aw_assert(FD_ISSET(inotifier, &readfds));
435 if (reactivate_stale) {
436 check_for_created_files();
437 reactivate_stale =
false;
440 #else // USE_STATPOLL
441 for (FileList::iterator fi = files.begin(); fi != files.end(); ++fi) {
442 TrackedFilePtr f = *fi;
443 f->callback_if_changed();
458 #if defined(TRACE_INOTIFY_BASIC)
459 fputs(
"[inotifier] - timer callback\n", stderr);
469 fprintf(stderr,
"Error in TrackedFiles: %s\n", err);
480 #if defined(TRACE_INOTIFY_BASIC)
481 fputs(
"[inotifier] + timer callback\n", stderr);
508 change_flag[
flag][reason]++;
510 #if defined(TRACE_INOTIFY)
511 fprintf(stderr,
"trace_file_changed_cb: flag=%i reason=%i file='%s'\n", flag,
int(reason), file);
516 #define BUFIDX(f,r) ((r)*(FLAGS+1)+(f))
518 static const char *update_change_counts() {
519 for (
int f = 0; f<FLAGS; ++f) {
521 change_flag[f][r] = 0;
529 for (
int f = 0; f<FLAGS; ++f) {
530 buf[BUFIDX(f,r)] =
'0'+change_flag[f][r];
532 buf[BUFIDX(FLAGS,r)] =
'-';
534 buf[BUFIDX(0,CHANGE_REASONS)-1] = 0;
542 const char *detected_counts = update_change_counts();
546 return all().ofgroup(expected);
549 #define TEST_EXPECT_CHANGE_COUNTS(expected) TEST_EXPECTATION(change_counts_are(expected))
551 static void update_file(
const char *filename) {
552 FILE *out = fopen(filename,
"wt");
558 inline void touch_files(
const char *f1,
const char *f2 = NULp) {
559 #if defined(USE_STATPOLL)
560 static time_t last = 0;
563 do time(&now);
while (now == last);
567 if (f1) update_file(f1);
568 if (f2) update_file(f2);
570 #if defined(USE_STATPOLL)
575 inline void erase_files(
const char *f1,
const char *f2 = NULp) {
580 inline void move_file(
const char *src,
const char *dst) {
583 inline void double_move_file(
const char *src,
const char *tmp,
const char *dst) {
588 #define INOTIFY_TESTDIR "inotify"
592 void TEST_inotifications() {
593 #if !defined(USE_INOTIFY)
594 # warning inotifications broken in poll mode
608 if (inotify_tests_failing_randomly) {
609 #if defined(RETRY_INOTIFICATIONS_TEST)
612 MISSING_TEST(
"TEST_inotifications fails randomly on build-jessie -> skipped!");
617 const char *inotify_testdir = INOTIFY_TESTDIR;
619 const char *testfile1 = INOTIFY_TESTDIR
"/inotify1.testfile";
620 const char *testfile2 = INOTIFY_TESTDIR
"/inotify2.testfile";
621 const char *nofile = INOTIFY_TESTDIR
"/no.testfile";
625 #if defined(RETRY_INOTIFICATIONS_TEST)
626 static bool retrying =
false;
629 erase_files(testfile1,testfile2);
638 FileChangedCallback fccb1 = makeFileChangedCallback(trace_file_changed_cb, 0);
639 FileChangedCallback fccb2 = makeFileChangedCallback(trace_file_changed_cb, 1);
640 FileChangedCallback fccb3 = makeFileChangedCallback(trace_file_changed_cb, 2);
641 FileChangedCallback dccb4 = makeFileChangedCallback(trace_file_changed_cb, 3);
643 maintain_timer_callback =
false;
647 TEST_EXPECT_CHANGE_COUNTS(
"0000-0000-0000");
651 touch_files(testfile1, testfile2); TEST_EXPECT_CHANGE_COUNTS(
"0002-0000-0000");
655 touch_files(testfile1, testfile2); TEST_EXPECT_CHANGE_COUNTS(
"1002-0000-0000");
659 touch_files(testfile1, testfile2); TEST_EXPECT_CHANGE_COUNTS(
"1102-0000-0000");
661 erase_files(testfile1); TEST_EXPECT_CHANGE_COUNTS(
"0001-1000-0000");
662 touch_files(testfile1); TEST_EXPECT_CHANGE_COUNTS(
"0001-0000-1000");
663 touch_files(testfile1); TEST_EXPECT_CHANGE_COUNTS(
"1001-0000-0000");
665 erase_files(testfile2); TEST_EXPECT_CHANGE_COUNTS(
"0001-0100-0000");
670 touch_files(nofile); TEST_EXPECT_CHANGE_COUNTS(
"0001-0000-0010");
671 erase_files(nofile); touch_files(nofile); TEST_EXPECT_CHANGE_COUNTS(
"0002-0000-0010");
672 erase_files(nofile); TEST_EXPECT_CHANGE_COUNTS(
"0001-0010-0000");
673 touch_files(nofile); erase_files(nofile); TEST_EXPECT_CHANGE_COUNTS(
"0002-0000-0000");
676 move_file(testfile1, nofile); TEST_EXPECT_CHANGE_COUNTS(
"0000-1000-0010");
677 touch_files(nofile); TEST_EXPECT_CHANGE_COUNTS(
"0011-0000-0000");
678 touch_files(testfile1); TEST_EXPECT_CHANGE_COUNTS(
"0001-0000-1000");
679 move_file(nofile, testfile1); TEST_EXPECT_CHANGE_COUNTS(
"0000-0010-1000");
680 double_move_file(testfile1, nofile, testfile1); TEST_EXPECT_CHANGE_COUNTS(
"0000-0000-1000");
681 double_move_file(testfile1, nofile, testfile2); TEST_EXPECT_CHANGE_COUNTS(
"0000-1000-0100");
682 touch_files(testfile1, nofile); TEST_EXPECT_CHANGE_COUNTS(
"0002-0000-1010");
683 double_move_file(testfile2, nofile, testfile1); TEST_EXPECT_CHANGE_COUNTS(
"0000-0110-1000");
688 touch_files(testfile1, testfile2); TEST_EXPECT_CHANGE_COUNTS(
"1012-0000-0110");
690 touch_files(testfile1); TEST_EXPECT_CHANGE_COUNTS(
"1011-0000-0000");
691 touch_files(testfile2); TEST_EXPECT_CHANGE_COUNTS(
"0111-0000-0000");
693 touch_files(testfile1, testfile2); TEST_EXPECT_CHANGE_COUNTS(
"1122-0000-0000");
695 touch_files(testfile1, testfile2); TEST_EXPECT_CHANGE_COUNTS(
"1122-0000-0000");
699 touch_files(testfile1, testfile2); TEST_EXPECT_CHANGE_COUNTS(
"0122-0000-0000");
702 touch_files(testfile1, testfile2);
705 TEST_EXPECT_CHANGE_COUNTS(
"0122-0000-0000");
711 touch_files(testfile1, testfile2); TEST_EXPECT_CHANGE_COUNTS(
"0022-0000-0000");
715 touch_files(testfile1, testfile2); TEST_EXPECT_CHANGE_COUNTS(
"0012-0000-0000");
719 touch_files(testfile1, testfile2); TEST_EXPECT_CHANGE_COUNTS(
"0002-0000-0000");
723 touch_files(testfile1, testfile2); TEST_EXPECT_CHANGE_COUNTS(
"0000-0000-0000");
#define TEST_EXPECT_MORE_EQUAL(val, ref)
static unsigned timed_inotifications_check_cb()
const char * arb_gethostname()
void add_callback(const FileChangedCallback &cb)
int ARB_stricmp(const char *s1, const char *s2)
GB_ERROR GB_IO_error(const char *action, const char *filename)
const string & get_name() const
TrackedFile(const string &filename)
static SmartPtr< TrackedFiles > allTrackers(new TrackedFiles)
void add_timed_callback(int ms, const TimedCallback &tcb)
int GB_unlink(const char *path)
SmartPtr< TrackedFile > TrackedFilePtr
void erase(string file, const FileChangedCallback &fccb)
static AW_root * SINGLETON
set< FileChangedCallback > CallbackList
void AW_remove_inotification(const char *file, const FileChangedCallback &fccb)
void callAll(ChangeReason reason) const
void insert(string file, const FileChangedCallback &fccb)
#define IF_TRACE_INOTIFY(x)
void GBK_terminate(const char *error) __ATTR__NORETURN
void AW_add_inotification(const char *file, const FileChangedCallback &fccb)
GB_ERROR GB_move_file(const char *oldpath, const char *newpath)
static void error(const char *msg)
expectation_group & add(const expectation &e)
static void show_tracked_error()
#define TEST_EXPECT_ZERO(cond)
fputs(TRACE_PREFIX, stderr)
static void copy(double **i, double **j)
#define MISSING_TEST(description)
#define TEST_EXPECT_NO_ERROR(call)
const unsigned AW_INOTIFY_TIMER
void GB_split_full_path(const char *fullpath, char **res_dir, char **res_fullname, char **res_name_only, char **res_suffix)
bool GB_is_regularfile(const char *path)
void callback_if_changed() const
void remove_callback(const FileChangedCallback &cb)
static bool maintain_timer_callback
GB_ERROR GB_create_directory(const char *path)