source: branches/stable/WINDOW/AW_inotify.cxx

Last change on this file was 18140, checked in by westram, 5 years ago
  • full update from child 'fix' into 'trunk'
    • improve target 'cleanRelinkable'
    • fix crash in inotify file tracker (occurred when moving a tracked file)
    • minor tweaks for fts
  • adds: log:branches/fix@18131:18139
File size: 24.6 KB
Line 
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
23using 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
53typedef set<FileChangedCallback> CallbackList;
54class TrackedFiles;
55
56class 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
78public:
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
142typedef SmartPtr<TrackedFile> TrackedFilePtr;
143
144class TrackedFiles : virtual Noncopyable {
145    typedef list<TrackedFilePtr> FileList;
146
147    FileList files;
148
149#if defined(USE_INOTIFY)
150    int inotifier;
151
152    RefPtr<const char> error;
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
214public:
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
239    GB_ERROR get_error() {
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)
314static 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
321void 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
343void TrackedFiles::check_changes() {
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
450static SmartPtr<TrackedFiles> allTrackers(new TrackedFiles);
451
452static bool    maintain_timer_callback = true;
453const unsigned AW_INOTIFY_TIMER        = 700; // ms
454
455static 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
466static 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
473void AW_add_inotification(const char *file, const FileChangedCallback& fccb) {
474    /*! callback after a file changes
475     * @param file the file to watch
476     * @param fccb callback to call
477     */
478    show_tracked_error();
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);
486    show_tracked_error();
487}
488
489void AW_remove_inotification(const char *file, const FileChangedCallback& fccb) {
490    allTrackers->erase(file, fccb);
491    show_tracked_error();
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
503const int FLAGS = 4;
504static int change_flag[FLAGS][CHANGE_REASONS];
505
506static 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
518static 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
538static 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
551static 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
558inline 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
575inline 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
580inline void move_file(const char *src, const char *dst) {
581    TEST_EXPECT_NO_ERROR(GB_move_file(src, dst));
582}
583inline 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
592void 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));
632        allTrackers = new TrackedFiles;
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 supressed)
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 supressed)
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// --------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.