ARB
AW_inotify.cxx
Go to the documentation of this file.
1 // =============================================================== //
2 // //
3 // File : AW_inotify.cxx //
4 // Purpose : watch file/directory changes //
5 // //
6 // Coded by Ralf Westram (coder@reallysoft.de) in October 2017 //
7 // http://www.arb-home.de/ //
8 // //
9 // =============================================================== //
10 
11 #include "aw_inotify.hxx"
12 
13 #include "aw_root.hxx"
14 #include <arbdbt.h>
15 #include <arb_file.h>
16 
17 #include <unistd.h>
18 
19 #include <string>
20 #include <list>
21 #include <set>
22 
23 using namespace std;
24 
25 #if defined(LINUX)
26 # define USE_INOTIFY
27 #endif
28 
29 #if !defined(USE_INOTIFY)
30 # define USE_STATPOLL
31 #endif
32 
33 #if defined(USE_STATPOLL)
34 #include <sys/stat.h>
35 #endif
36 
37 #if defined(USE_INOTIFY)
38 #include <sys/inotify.h>
39 #endif
40 
41 #if defined(DEBUG)
42 // #define TRACE_INOTIFY
43 // #define TRACE_INOTIFY_BASIC
44 #endif
45 
46 #if defined(TRACE_INOTIFY)
47 #define IF_TRACE_INOTIFY(x) x
48 #else // !TRACE_INOTIFY
49 #define IF_TRACE_INOTIFY(x)
50 #endif
51 
52 
53 typedef set<FileChangedCallback> CallbackList;
54 class TrackedFiles;
55 
56 class TrackedFile : public Noncopyable {
57  string file;
58  CallbackList callbacks;
59 
60 #if defined(USE_INOTIFY)
61  int watch_descriptor;
62 #else // USE_STATPOLL
63 
64  mutable int lastModtime; // last known modification time of 'file'
65  int getModtime() const {
66  struct stat st;
67  if (stat(file.c_str(), &st) == 0) return st.st_mtime;
68  return 0;
69  }
70  bool was_changed() const {
71  int currModtime = getModtime();
72  bool changed = currModtime>lastModtime;
73  lastModtime = currModtime;
74  return changed;
75  }
76 #endif
77 
78 public:
79  TrackedFile(const string& filename) :
80  file(filename)
81  {
82 #if defined(USE_INOTIFY)
83  watch_descriptor = -1;
84 #else // USE_STATPOLL
85  lastModtime = 0;
86 #endif
87  }
88 #if defined(USE_INOTIFY)
89  ~TrackedFile() {
90  aw_assert(watch_descriptor == -1); // watch has to be removed before destruction
91  }
92 #endif
93 
94  const string& get_name() const { return file; }
95 
96  void add_callback (const FileChangedCallback& cb) { callbacks.insert(cb); }
97  void remove_callback(const FileChangedCallback& cb) { callbacks.erase(cb); }
98 
99  bool empty() const { return callbacks.empty(); }
100 
101  void callAll(ChangeReason reason) const {
102  bool deleted_still_exists = reason == CR_DELETED && GB_is_regularfile(file.c_str());
103  if (!deleted_still_exists) {
104  CallbackList copy = callbacks;
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);
108  }
109  }
110  }
111  }
112 
113 #if defined(USE_INOTIFY)
114  int get_watch_descriptor() const {
115  return watch_descriptor;
116  }
117  GB_ERROR add_watch(int inotifier) {
118  aw_assert(watch_descriptor == -1);
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;
121  }
122  GB_ERROR remove_watch(int inotifier) {
123  GB_ERROR err = NULp;
124  if (watch_descriptor != -1) {
125  if (inotify_rm_watch(inotifier, watch_descriptor)<0) {
126  err = GB_IO_error("un-watching", file.c_str());
127  }
128  watch_descriptor = -1;
129  }
130  return err;
131  }
132  void mark_as_disabled() {
133  watch_descriptor = -1;
134  }
135  void track_creation(TrackedFiles *tracked);
136 
137 #else // USE_STATPOLL
138  void callback_if_changed() const { if (was_changed()) callAll(CR_MODIFIED); }
139 #endif
140 };
141 
143 
144 class TrackedFiles : virtual Noncopyable {
145  typedef list<TrackedFilePtr> FileList;
146 
147  FileList files;
148 
149 #if defined(USE_INOTIFY)
150  int inotifier;
151 
153 
154  SmartCharPtr ievent_buffer;
155  int oversize;
156 
157  bool reactivate_stale;
158 
159  size_t get_ievent_buffer_size() {
160  return sizeof(inotify_event)+oversize;
161  }
162  void realloc_buffer(int new_oversize) {
163  aw_assert(new_oversize>oversize);
164  oversize = new_oversize;
165  ievent_buffer = ARB_alloc<char>(get_ievent_buffer_size());
166  }
167 
168  inotify_event *get_ievent_buffer() {
169  if (oversize<0) realloc_buffer(9*sizeof(inotify_event)); // reserve memory for 10 events
170  return reinterpret_cast<inotify_event*>(&*ievent_buffer);
171  }
172  void increase_buffer_oversize() {
173  aw_assert(oversize>0);
174  realloc_buffer(oversize*3/2);
175  }
176 
177 
178 #endif
179 
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;
183  }
184  return files.end();
185  }
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()) {
190  return f;
191  }
192  }
193  return files.end();
194  }
195 
196  void check_for_created_files() {
197  aw_assert(reactivate_stale);
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);
202  if (!err) { // successfully reactivated
203  f->callAll(CR_CREATED);
204  }
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());
207 #endif
208  }
209  }
210  // @@@ if no "stale" watch descriptor left => uninstall all watches of parent_modified_cb
211  }
212 #endif
213 
214 public:
215 #if defined(USE_INOTIFY)
216  TrackedFiles() :
217  error(NULp),
218  oversize(-1),
219  reactivate_stale(false)
220  {
221  inotifier = inotify_init();
222  if (inotifier<0) {
223  error = GB_IO_error("creating", "<inotify-instance>");
224  }
225  }
226  ~TrackedFiles() {
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);
232  }
233  aw_assert(f->get_watch_descriptor() == -1);
234  }
235  close(inotifier);
236  }
237 #endif
238 
240 #if defined(USE_INOTIFY)
241  GB_ERROR err = error;
242  error = NULp;
243  return err;
244 #else // USE_STATPOLL
245  return NULp;
246 #endif
247  }
248 
249  bool empty() const {
250  return files.empty();
251  }
252  void insert(string file, const FileChangedCallback& fccb) {
253  // @@@ use canonical path of file!
254 
255 #if defined(TRACE_INOTIFY_BASIC)
256  fprintf(stderr, "[inotifier] + %s\n", file.c_str());
257 #endif
258 
259 #if defined(USE_INOTIFY)
260  aw_assert(!error);
261 #endif
262  FileList::iterator foundIter = find(file);
263  TrackedFilePtr found;
264 
265  if (foundIter != files.end()) {
266  found = *foundIter;
267  }
268  else {
269  found = new TrackedFile(file);
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());
276 #endif
277 #endif
278  }
279  found->add_callback(fccb);
280  }
281  void erase(string file, const FileChangedCallback& fccb) {
282  // @@@ use canonical path of file!
283 
284 #if defined(TRACE_INOTIFY_BASIC)
285  fprintf(stderr, "[inotifier] - %s\n", file.c_str());
286 #endif
287 
288 #if defined(USE_INOTIFY)
289  aw_assert(!error);
290 #endif
291 
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);
300  }
301 #endif
302  files.erase(foundIter);
303  }
304  }
305  }
306 
307  void check_changes();
308 #if defined(USE_INOTIFY)
309  void request_reactivate_stale() { reactivate_stale = true; }
310 #endif
311 };
312 
313 #if defined(USE_INOTIFY)
314 static void parent_modified_cb(const char *IF_TRACE_INOTIFY(parent_dir), ChangeReason IF_TRACE_INOTIFY(reason), TrackedFiles *tracked) {
315 #if defined(TRACE_INOTIFY)
316  fprintf(stderr, "parent_modified_cb(dir='%s', reason=%i) called\n", parent_dir, int(reason));
317 #endif
318  tracked->request_reactivate_stale();
319 }
320 
321 void TrackedFile::track_creation(TrackedFiles *tracked) { // @@@ creation not tracked if !USE_INOTIFY
322  aw_assert(get_watch_descriptor() == -1); // otherwise it exists
323 
324 #if defined(TRACE_INOTIFY)
325  fprintf(stderr, "TrackedFile::track_creation: file='%s'\n", file.c_str());
326 #endif
327 
328  char *parent_dir;
329  GB_split_full_path(file.c_str(), &parent_dir, NULp, NULp, NULp);
330 
331 #if defined(TRACE_INOTIFY)
332  fprintf(stderr, "track_creation: parent_dir='%s'\n", parent_dir);
333 #endif
334 
335  aw_assert(parent_dir);
336  if (parent_dir) {
337  AW_add_inotification(parent_dir, makeFileChangedCallback(parent_modified_cb, tracked));
338  free(parent_dir);
339  }
340 }
341 #endif
342 
344  // called manually from unittest below
345  // called via timer callback otherwise
346 
347 #if defined(USE_INOTIFY)
348  aw_assert(!error);
349 
350  timespec timeout = { 0, 0 }; // don't wait
351  fd_set readfds;
352  FD_ZERO(&readfds);
353  FD_SET(inotifier, &readfds);
354 
355  int sel;
356  while ((sel = pselect(inotifier+1, &readfds, NULp, NULp, &timeout, NULp))) { // only poll if events are waiting
357  aw_assert(sel>0);
358 
359  inotify_event *ievent = get_ievent_buffer();
360  ssize_t got = read(inotifier, ievent, get_ievent_buffer_size());
361 
362  if (got >= ssize_t(sizeof(inotify_event))) {
363  while (got>0) {
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 : "";
370 
371  fprintf(stderr,
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);
374 #endif
375  if (watch_removed) {
376  found->mark_as_disabled();
377  found->track_creation(this);
378  }
379  else if (ievent->mask & IN_DELETE_SELF) {
380  found->callAll(CR_DELETED);
381  }
382  else if (ievent->mask & IN_MOVE_SELF) {
383  found->callAll(CR_DELETED);
384  found->remove_watch(inotifier); // invalidate watch descriptor of found
385  found->track_creation(this); // ensure re-creation gets detected
386  reactivate_stale = true; // causes check whether new target has a "stale" watch
387  }
388  else if (ievent->mask & (IN_DELETE|IN_CLOSE_WRITE)) { // item deleted from directory or file/dir modified
389  found->callAll(CR_MODIFIED);
390  }
391  else {
392  aw_assert(0); // unhandled event from inotify
393  }
394  }
395  else {
396 #if defined(TRACE_INOTIFY)
397  fprintf(stderr, "ignoring event for unknown watch_descriptor=%i\n", ievent->wd);
398 #endif
399  }
400 
401  int event_len = sizeof(inotify_event)+ievent->len;
402  got -= event_len;
403  ievent = reinterpret_cast<inotify_event*>(reinterpret_cast<char*>(ievent)+event_len);
404  }
405  }
406  else {
407  bool buffer_too_small = false;
408  GB_ERROR read_err = NULp;
409 
410  if (got == -1) {
411  if (errno == EINVAL) buffer_too_small = true;
412  else read_err = GB_IO_error("reading", "<inotify-descriptor>");
413  }
414  else if (got == 0) buffer_too_small = true;
415  else {
416  aw_assert(0);
417  }
418 
419  if (read_err) {
420  fprintf(stderr, "inotifier broken: %s\n", read_err);
421  aw_assert(0);
422  break; // while loop
423  }
424 
425  if (!buffer_too_small) {
426  aw_assert(buffer_too_small);
427  GBK_terminate("inotify event queue broken? (neither buffer_too_small nor read_err)");
428  }
429  increase_buffer_oversize();
430  }
431 
432  aw_assert(FD_ISSET(inotifier, &readfds));
433  }
434 
435  if (reactivate_stale) {
436  check_for_created_files();
437  reactivate_stale = false; // do once if any dir-content changes
438  }
439 
440 #else // USE_STATPOLL
441  for (FileList::iterator fi = files.begin(); fi != files.end(); ++fi) {
442  TrackedFilePtr f = *fi;
443  f->callback_if_changed();
444  }
445 #endif
446 }
447 
448 // --------------------------------------------------------------------------------
449 
451 
452 static bool maintain_timer_callback = true;
453 const unsigned AW_INOTIFY_TIMER = 700; // ms
454 
455 static unsigned timed_inotifications_check_cb() {
456  allTrackers->check_changes();
457  if (allTrackers->empty()) {
458 #if defined(TRACE_INOTIFY_BASIC)
459  fputs("[inotifier] - timer callback\n", stderr);
460 #endif
461  return 0;
462  }
463  return AW_INOTIFY_TIMER;
464 }
465 
466 static void show_tracked_error() {
467  GB_ERROR err = allTrackers->get_error();
468  if (err) {
469  fprintf(stderr, "Error in TrackedFiles: %s\n", err);
470  }
471 }
472 
473 void AW_add_inotification(const char *file, const FileChangedCallback& fccb) {
479  if (allTrackers->empty() & maintain_timer_callback) {
480 #if defined(TRACE_INOTIFY_BASIC)
481  fputs("[inotifier] + timer callback\n", stderr);
482 #endif
483  AW_root::SINGLETON->add_timed_callback(AW_INOTIFY_TIMER, makeTimedCallback(timed_inotifications_check_cb));
484  }
485  allTrackers->insert(file, fccb);
487 }
488 
489 void AW_remove_inotification(const char *file, const FileChangedCallback& fccb) {
490  allTrackers->erase(file, fccb);
492 }
493 
494 // --------------------------------------------------------------------------------
495 
496 #ifdef UNIT_TESTS
497 #ifndef TEST_UNIT_H
498 #include <test_unit.h>
499 #endif
500 #include <arb_cs.h>
501 #include <arb_str.h>
502 
503 const int FLAGS = 4;
504 static int change_flag[FLAGS][CHANGE_REASONS];
505 
506 static void trace_file_changed_cb(const char *IF_TRACE_INOTIFY(file), ChangeReason reason, int flag) {
507  aw_assert(flag<FLAGS);
508  change_flag[flag][reason]++;
509 
510 #if defined(TRACE_INOTIFY)
511  fprintf(stderr, "trace_file_changed_cb: flag=%i reason=%i file='%s'\n", flag, int(reason), file);
512 #endif
513 }
514 
515 // buf layout is "0123-0123-0123" (digits here are flag indices)
516 #define BUFIDX(f,r) ((r)*(FLAGS+1)+(f))
517 
518 static const char *update_change_counts() {
519  for (int f = 0; f<FLAGS; ++f) {
520  for (int r = 0; r<CHANGE_REASONS; ++r) {
521  change_flag[f][r] = 0;
522  }
523  }
524 
525  allTrackers->check_changes();
526 
527  static char buf[BUFIDX(0,CHANGE_REASONS)];
528  for (int r = 0; r<CHANGE_REASONS; ++r) {
529  for (int f = 0; f<FLAGS; ++f) {
530  buf[BUFIDX(f,r)] = '0'+change_flag[f][r];
531  }
532  buf[BUFIDX(FLAGS,r)] = '-';
533  }
534  buf[BUFIDX(0,CHANGE_REASONS)-1] = 0;
535  return buf;
536 }
537 
538 static arb_test::match_expectation change_counts_are(const char *expected_counts) {
539  using namespace arb_test;
540  expectation_group expected;
541 
542  const char *detected_counts = update_change_counts();
543 
544  expected.add(that(detected_counts).is_equal_to(expected_counts));
545 
546  return all().ofgroup(expected);
547 }
548 
549 #define TEST_EXPECT_CHANGE_COUNTS(expected) TEST_EXPECTATION(change_counts_are(expected))
550 
551 static void update_file(const char *filename) {
552  FILE *out = fopen(filename, "wt");
553  aw_assert(out);
554  fputs("hi", out);
555  fclose(out);
556 }
557 
558 inline void touch_files(const char *f1, const char *f2 = NULp) {
559 #if defined(USE_STATPOLL)
560  static time_t last = 0;
561  {
562  time_t now;
563  do time(&now); while (now == last); // wait for new second (to ensure timestamps differ)
564  }
565 #endif
566 
567  if (f1) update_file(f1);
568  if (f2) update_file(f2);
569 
570 #if defined(USE_STATPOLL)
571  time(&last);
572 #endif
573 }
574 
575 inline void erase_files(const char *f1, const char *f2 = NULp) {
576  if (f1) TEST_EXPECT_MORE_EQUAL(GB_unlink(f1), 0);
577  if (f2) TEST_EXPECT_MORE_EQUAL(GB_unlink(f2), 0);
578 }
579 
580 inline void move_file(const char *src, const char *dst) {
582 }
583 inline void double_move_file(const char *src, const char *tmp, const char *dst) {
584  move_file(src, tmp);
585  move_file(tmp, dst);
586 }
587 
588 #define INOTIFY_TESTDIR "inotify"
589 
590 // #define RETRY_INOTIFICATIONS_TEST
591 
592 void TEST_inotifications() {
593 #if !defined(USE_INOTIFY)
594 # warning inotifications broken in poll mode
595  MISSING_TEST(TEST_inotifications);
596  // @@@ fix behavior in poll mode (tests below fail)
597  // functionality needed in arb seems to work nevertheless (maybe just test less if !USE_INOTIFY?)
598  return;
599 #endif
600 
601  // for some unknown reason this test randomly fails on one build host
602  //
603  // Update: the previous changes (log:trunk@16775:16780) do not fix the problem :(
604  // * if the test fails initially, repeated calls always fail!
605  // * but initial failure only happens with 5-10% probability (of calls of test-executable)
606  //
607  static bool inotify_tests_failing_randomly = ARB_stricmp(arb_gethostname(), "build-jessie") == 0;
608  if (inotify_tests_failing_randomly) {
609 #if defined(RETRY_INOTIFICATIONS_TEST)
610  TEST_ALLOW_RETRY(5); // tell test-suite to try this test up to 5 times
611 #else
612  MISSING_TEST("TEST_inotifications fails randomly on build-jessie -> skipped!");
613  return; // disable test on this host
614 #endif
615  }
616 
617  const char *inotify_testdir = INOTIFY_TESTDIR;
618 
619  const char *testfile1 = INOTIFY_TESTDIR "/inotify1.testfile";
620  const char *testfile2 = INOTIFY_TESTDIR "/inotify2.testfile";
621  const char *nofile = INOTIFY_TESTDIR "/no.testfile";
622 
623  TEST_EXPECT_NO_ERROR(GB_create_directory(INOTIFY_TESTDIR));
624 
625 #if defined(RETRY_INOTIFICATIONS_TEST)
626  static bool retrying = false;
627  if (retrying) {
628  // remove any leftovers from last try
629  erase_files(testfile1,testfile2);
630  erase_files(nofile);
631  TEST_EXPECT_ZERO(rmdir(inotify_testdir));
633  TEST_EXPECT_NO_ERROR(GB_create_directory(INOTIFY_TESTDIR));
634  }
635  retrying = true;
636 #endif
637 
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); // tracks directory
642 
643  maintain_timer_callback = false;
644  erase_files(nofile); // make sure file does not exist
645 
646  // event order (= order of ChangeReason) is "MODIFIED-DELETED-UNKNOWN"
647  TEST_EXPECT_CHANGE_COUNTS("0000-0000-0000");
648 
649  AW_add_inotification(inotify_testdir, dccb4);
650 
651  touch_files(testfile1, testfile2); TEST_EXPECT_CHANGE_COUNTS("0002-0000-0000"); // two cbs for two changes in directory
652 
653  AW_add_inotification(testfile1, fccb1);
654 
655  touch_files(testfile1, testfile2); TEST_EXPECT_CHANGE_COUNTS("1002-0000-0000"); // two cbs for dir-changes; one cb for testfile1
656 
657  AW_add_inotification(testfile2, fccb2);
658 
659  touch_files(testfile1, testfile2); TEST_EXPECT_CHANGE_COUNTS("1102-0000-0000");
660 
661  erase_files(testfile1); TEST_EXPECT_CHANGE_COUNTS("0001-1000-0000"); // dir changed + testfile1 deleted
662  touch_files(testfile1); TEST_EXPECT_CHANGE_COUNTS("0001-0000-1000"); // dir changed + testfile1 (re-)created
663  touch_files(testfile1); TEST_EXPECT_CHANGE_COUNTS("1001-0000-0000"); // dir changed + testfile1 modified
664 
665  erase_files(testfile2); TEST_EXPECT_CHANGE_COUNTS("0001-0100-0000"); // dir changed + testfile2 deleted
666 
667  AW_add_inotification(nofile, fccb3); // add a tracker on non-existing file
668 
669  // touch non-yet-existing 'nofile' -> should detect creation
670  touch_files(nofile); TEST_EXPECT_CHANGE_COUNTS("0001-0000-0010"); // detects creation of non-existing file
671  erase_files(nofile); touch_files(nofile); TEST_EXPECT_CHANGE_COUNTS("0002-0000-0010"); // does detect quick delete+recreate as "create" (delete gets suppressed)
672  erase_files(nofile); TEST_EXPECT_CHANGE_COUNTS("0001-0010-0000"); // detects erase
673  touch_files(nofile); erase_files(nofile); TEST_EXPECT_CHANGE_COUNTS("0002-0000-0000"); // does NOT detect quick create+delete (just 2 directory changes)
674 
675  // test moving files
676  move_file(testfile1, nofile); TEST_EXPECT_CHANGE_COUNTS("0000-1000-0010"); // detects delete 'testfile1' and create 'nofile'
677  touch_files(nofile); TEST_EXPECT_CHANGE_COUNTS("0011-0000-0000"); // detects modify 'nofile'
678  touch_files(testfile1); TEST_EXPECT_CHANGE_COUNTS("0001-0000-1000"); // detects create 'testfile1'
679  move_file(nofile, testfile1); TEST_EXPECT_CHANGE_COUNTS("0000-0010-1000"); // detects delete 'nofile' and create 'testfile1' (delete of overwritten 'testfile1' gets suppressed)
680  double_move_file(testfile1, nofile, testfile1); TEST_EXPECT_CHANGE_COUNTS("0000-0000-1000"); // detects creation of 'testfile1' ('nofile' does not trigger)
681  double_move_file(testfile1, nofile, testfile2); TEST_EXPECT_CHANGE_COUNTS("0000-1000-0100"); // delete 'testfile1' + create 'testfile2' ('nofile' does not trigger)
682  touch_files(testfile1, nofile); TEST_EXPECT_CHANGE_COUNTS("0002-0000-1010"); // create 'testfile1'+'nofile'
683  double_move_file(testfile2, nofile, testfile1); TEST_EXPECT_CHANGE_COUNTS("0000-0110-1000"); // delete 'testfile2' and 'nofile' + create 'testfile1'
684 
685  AW_add_inotification(testfile1, fccb3);
686  AW_add_inotification(testfile2, fccb3);
687 
688  touch_files(testfile1, testfile2); TEST_EXPECT_CHANGE_COUNTS("1012-0000-0110"); // dir changed + testfile1 modified + testfile2 created
689 
690  touch_files(testfile1); TEST_EXPECT_CHANGE_COUNTS("1011-0000-0000"); // 2 cbs for file + 1 cb for dir triggered by one touch
691  touch_files(testfile2); TEST_EXPECT_CHANGE_COUNTS("0111-0000-0000"); // same for other testfile
692 
693  touch_files(testfile1, testfile2); TEST_EXPECT_CHANGE_COUNTS("1122-0000-0000"); // 6 callbacks triggered by 2 touches
694  AW_add_inotification(testfile2, fccb3); // (try to) add a tracker twice
695  touch_files(testfile1, testfile2); TEST_EXPECT_CHANGE_COUNTS("1122-0000-0000"); // still 6 callbacks triggered by 2 touches
696 
697  AW_remove_inotification(testfile1, fccb1);
698 
699  touch_files(testfile1, testfile2); TEST_EXPECT_CHANGE_COUNTS("0122-0000-0000");
700 
701  // changing cd is ok for triggers @@@ might fail for delete+re-create
702  touch_files(testfile1, testfile2);
703  {
704  TEST_EXPECT_ZERO(chdir(inotify_testdir));
705  TEST_EXPECT_CHANGE_COUNTS("0122-0000-0000");
706  TEST_EXPECT_ZERO(chdir(".."));
707  }
708 
709  AW_remove_inotification(testfile2, fccb2);
710 
711  touch_files(testfile1, testfile2); TEST_EXPECT_CHANGE_COUNTS("0022-0000-0000");
712 
713  AW_remove_inotification(testfile1, fccb3);
714 
715  touch_files(testfile1, testfile2); TEST_EXPECT_CHANGE_COUNTS("0012-0000-0000");
716 
717  AW_remove_inotification(testfile2, fccb3);
718 
719  touch_files(testfile1, testfile2); TEST_EXPECT_CHANGE_COUNTS("0002-0000-0000");
720 
721  AW_remove_inotification(inotify_testdir, dccb4);
722 
723  touch_files(testfile1, testfile2); TEST_EXPECT_CHANGE_COUNTS("0000-0000-0000");
724 
725  TEST_EXPECT_ZERO(GB_unlink(testfile1));
726  TEST_EXPECT_ZERO(GB_unlink(testfile2));
727  TEST_EXPECT_ZERO(rmdir(inotify_testdir));
728 
729  allTrackers = new TrackedFiles; // test cleanup (normally occurs at program exit)
730 }
731 
732 #endif // UNIT_TESTS
733 
734 // --------------------------------------------------------------------------------
#define TEST_EXPECT_MORE_EQUAL(val, ref)
Definition: test_unit.h:1310
group_matcher all()
Definition: test_unit.h:1011
static unsigned timed_inotifications_check_cb()
Definition: AW_inotify.cxx:455
const char * arb_gethostname()
Definition: arb_cs.cxx:51
void add_callback(const FileChangedCallback &cb)
Definition: AW_inotify.cxx:96
int ARB_stricmp(const char *s1, const char *s2)
Definition: arb_str.h:28
bool empty() const
Definition: gb_cb.h:73
GB_ERROR GB_IO_error(const char *action, const char *filename)
Definition: arb_msg.cxx:285
const string & get_name() const
Definition: AW_inotify.cxx:94
TrackedFile(const string &filename)
Definition: AW_inotify.cxx:79
static SmartPtr< TrackedFiles > allTrackers(new TrackedFiles)
STL namespace.
bool empty() const
Definition: AW_inotify.cxx:249
void add_timed_callback(int ms, const TimedCallback &tcb)
Definition: AW_root.cxx:538
int GB_unlink(const char *path)
Definition: arb_file.cxx:188
#define cb(action)
SmartPtr< TrackedFile > TrackedFilePtr
Definition: AW_inotify.cxx:142
#define CHANGE_REASONS
Definition: aw_inotify.hxx:24
void erase(string file, const FileChangedCallback &fccb)
Definition: AW_inotify.cxx:281
static AW_root * SINGLETON
Definition: aw_root.hxx:102
bool empty() const
Definition: AW_inotify.cxx:99
set< FileChangedCallback > CallbackList
Definition: AW_inotify.cxx:53
void AW_remove_inotification(const char *file, const FileChangedCallback &fccb)
Definition: AW_inotify.cxx:489
void callAll(ChangeReason reason) const
Definition: AW_inotify.cxx:101
void insert(string file, const FileChangedCallback &fccb)
Definition: AW_inotify.cxx:252
#define IF_TRACE_INOTIFY(x)
Definition: AW_inotify.cxx:49
Generic smart pointer.
Definition: smartptr.h:149
void GBK_terminate(const char *error) __ATTR__NORETURN
Definition: arb_msg.cxx:509
void AW_add_inotification(const char *file, const FileChangedCallback &fccb)
Definition: AW_inotify.cxx:473
#define aw_assert(bed)
Definition: aw_position.hxx:29
GB_ERROR GB_move_file(const char *oldpath, const char *newpath)
Definition: arb_file.cxx:284
static void error(const char *msg)
Definition: mkptypes.cxx:96
expectation_group & add(const expectation &e)
Definition: test_unit.h:812
#define that(thing)
Definition: test_unit.h:1043
static void show_tracked_error()
Definition: AW_inotify.cxx:466
#define is_equal_to(val)
Definition: test_unit.h:1025
#define TEST_EXPECT_ZERO(cond)
Definition: test_unit.h:1085
long int flag
Definition: f2c.h:39
fputs(TRACE_PREFIX, stderr)
static void copy(double **i, double **j)
Definition: trnsprob.cxx:32
#define MISSING_TEST(description)
Definition: test_unit.h:1098
ChangeReason
Definition: aw_inotify.hxx:18
void check_changes()
Definition: AW_inotify.cxx:343
#define TEST_EXPECT_NO_ERROR(call)
Definition: test_unit.h:1118
const unsigned AW_INOTIFY_TIMER
Definition: AW_inotify.cxx:453
void GB_split_full_path(const char *fullpath, char **res_dir, char **res_fullname, char **res_name_only, char **res_suffix)
Definition: adsocket.cxx:1268
#define NULp
Definition: cxxforward.h:116
bool GB_is_regularfile(const char *path)
Definition: arb_file.cxx:76
void callback_if_changed() const
Definition: AW_inotify.cxx:138
void remove_callback(const FileChangedCallback &cb)
Definition: AW_inotify.cxx:97
GB_ERROR get_error()
Definition: AW_inotify.cxx:239
static bool maintain_timer_callback
Definition: AW_inotify.cxx:452
GB_ERROR GB_create_directory(const char *path)