00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037
00038
00039
00040
00041
00042
00043
00044
00045
00046
00047
00048
00049
00050
00051
00052
00053
00054
00055
00056
00057
00058
00059
00060
00061
00062
00063
00064
00065
00066
00067
00068
00069
00070
00071
00072
00073
00074
00075
00076
00077
00078
00079
00080
00081
00082
00083
00084
00085
00086
00087
00088
00089
00090
00091
00092
00093
00094
00095
00096
00097
00098
00099
00100
00101
00102
00103
00104
00105
00106
00107
00108
00109
00110
00111
00112
00113
00114
00115
00116
00117
00118
00119
00120
00121
00122
00123
00124
00125
00126
00127
00128
00129
00130
00131
00132
00133
00134
00135 #define _GNU_SOURCE
00136 #include <stdio.h>
00137 #include <stdlib.h>
00138 #include <stdarg.h>
00139 #include <stdint.h>
00140 #include <string.h>
00141 #include <errno.h>
00142 #include <getopt.h>
00143 #include <unistd.h>
00144
00145 #include <sys/types.h>
00146 #include <sys/stat.h>
00147 #include <sys/param.h>
00148 #include <sys/stat.h>
00149 #include <sys/inotify.h>
00150 #include <signal.h>
00151
00152 #include "dhash.h"
00153 #include "path_utils.h"
00154 #include "logging.h"
00155 #include "inotify_watch.h"
00156 #include "util.h"
00157 #include "lwatch.h"
00158
00159
00160
00161
00162
00163
00164
00165
00166 #define INOTIFY_EVENT_SIZE (sizeof (struct inotify_event))
00167
00168
00169
00170
00171
00172 #define INOTIFY_COOKIE_CACHE_SIZE 10
00173
00174
00175
00176
00177 #define INOTIFY_EVENTS (IN_MOVE | IN_CREATE | IN_ISDIR | IN_MODIFY | IN_OPEN | \
00178 IN_CLOSE_WRITE | IN_DELETE | IN_DELETE_SELF | \
00179 IN_IGNORED | IN_MOVED_FROM | IN_MOVED_TO)
00180
00181
00182
00183
00184 #define INITIAL_HASH_TABLE_SIZE 16
00185
00186
00187
00188
00189
00190 #define INITIAL_DESCENDANT_HASH_TABLE_SIZE 2
00191
00192
00193
00194
00195
00196
00197
00198
00199
00200
00201 struct inotify_cookie_cache_entry_t {
00202 uint32_t cookie;
00203 int prev_watch_id;
00204 int new_watch_id;
00205 char prev_path[PATH_MAX];
00206 char new_path[PATH_MAX];
00207 };
00208
00209
00210
00211
00212
00213 struct path_and_result_t {
00214 const char *path;
00215 bool result;
00216 };
00217
00218
00219
00220
00221
00222
00223
00224 typedef void (*inotify_lost_cookie_callback_t)(struct inotify_cookie_cache_entry_t *move);
00225
00226
00227
00228
00229
00230
00231
00232
00233
00234 static bool visit_descendants_and_test_for_ancestor(hash_entry_t *item, void *user_data);
00235 static bool is_path_an_ancestor_of_any_watch_descendants(struct path_watch_t *pw, const char *subject_path);
00236 static int free_path_watch(struct path_watch_t **ppw);
00237 static int new_path_watch(const char *path, struct path_watch_t **pw_return);
00238 static int destroy_path_watch(struct path_watch_t **ppw);
00239 static int destroy_path_watch_if_empty(struct path_watch_t **ppw);
00240 static int add_path_to_target_list(const char *path);
00241 static int remove_path_from_target_list(const char *path);
00242 static int track_path_watch(struct path_watch_t *pw);
00243 static int untrack_path_watch(struct path_watch_t *pw);
00244 static void lost_rename_callback(struct inotify_cookie_cache_entry_t *move);
00245 static int process_event(struct inotify_event *event);
00246 static int relocate_watches(struct path_watch_t *pw);
00247 static struct inotify_cookie_cache_entry_t *
00248 inotify_cookie_cache_operator(struct inotify_event *event, const char *path);
00249 static void flush_inotify_cookie_cache(void);
00250 static void log_watch_path_table(const char *fmt, ...);
00251 static void log_target_table(const char *fmt, ...);
00252
00253
00254
00255
00256
00257 bool keep_watching = true;
00258
00259
00260
00261
00262
00263
00264
00265
00266
00267
00268 static inotify_lost_cookie_callback_t inotify_lost_cookie_callback = NULL;
00269
00270
00271
00272
00273 static struct inotify_cookie_cache_entry_t inotify_cookie_cache[INOTIFY_COOKIE_CACHE_SIZE];
00274
00275
00276
00277
00278 static int n_cookies = 0;
00279
00280
00281
00282
00283
00284 static hash_table_t *watch_path_table = NULL;
00285
00286
00287
00288
00289 static hash_table_t *watch_id_table = NULL;
00290
00291
00292
00293 static hash_table_t *target_table = NULL;
00294
00295
00296
00297
00298 static int inotify_fd = -1;
00299
00300
00301
00302
00303
00304
00305 static lwatch_event_callback_t lwatch_event_callback = NULL;
00306
00307
00308
00309
00310
00311
00312
00313
00314
00315
00316
00317
00318
00319
00320
00321
00322
00323
00324 static bool visit_descendants_and_test_for_ancestor(hash_entry_t *item, void *user_data)
00325 {
00326 struct path_and_result_t *data = (struct path_and_result_t *) user_data;
00327 const char *descendant_path = item->key.str;
00328 const char *subject_path = data->path;
00329
00330 data->result = is_ancestor_path(subject_path, descendant_path);
00331 return !data->result;
00332 }
00333
00334
00335
00336
00337
00338
00339
00340
00341
00342
00343
00344
00345
00346
00347
00348
00349
00350
00351
00352
00353
00354
00355
00356
00357
00358
00359
00360
00361
00362
00363
00364
00365
00366
00367
00368
00369
00370
00371
00372
00373 static bool is_path_an_ancestor_of_any_watch_descendants(struct path_watch_t *pw, const char *subject_path)
00374 {
00375 struct path_and_result_t data;
00376
00377 data.path = subject_path;
00378 data.result = false;
00379
00380 hash_iterate(pw->descendant_path_table, visit_descendants_and_test_for_ancestor, &data);
00381 return data.result;
00382 }
00383
00384
00385
00386
00387
00388
00389
00390 static int free_path_watch(struct path_watch_t **ppw)
00391 {
00392 struct path_watch_t *pw = *ppw;
00393 int error = SUCCESS;
00394
00395 if (pw->descendant_path_table) {
00396 if ((error = hash_destroy(pw->descendant_path_table)) != HASH_SUCCESS) {
00397 log_msg(LOG_ERROR, _("could not free file hash table error=%d\n"), error);
00398 }
00399 }
00400
00401 free(pw);
00402 *ppw = NULL;
00403
00404 return error;
00405 }
00406
00407
00408
00409
00410
00411
00412
00413
00414 int new_path_watch(const char *path, struct path_watch_t **pw_return)
00415 {
00416 struct path_watch_t *pw;
00417 int error;
00418
00419 *pw_return = NULL;
00420
00421 if ((pw = (struct path_watch_t *)malloc(sizeof(struct path_watch_t))) == NULL) {
00422 error = ENOMEM;
00423 log_msg(LOG_ERROR, _("\"%s\" (%s)\n"), path, error_string(error));
00424 return error;
00425 }
00426
00427 if ((error = copy_path(pw->path, path, sizeof(pw->path))) != SUCCESS) {
00428 log_msg(LOG_ERROR, _("failed to copy path \"%s\" (%s)\n"), path, error_string(error));
00429 free_path_watch(&pw);
00430 return error;
00431 }
00432
00433 pw->watch_id = -1;
00434 pw->descendant_path_table = NULL;
00435
00436 if ((error = hash_create(INITIAL_DESCENDANT_HASH_TABLE_SIZE,
00437 &pw->descendant_path_table, NULL)) != HASH_SUCCESS) {
00438 log_msg(LOG_ERROR, _("cannot create file hash table in path_watch_t object for path \"%s\"\n"), path);
00439 free_path_watch(&pw);
00440 return error;
00441 }
00442
00443 log_msg(LOG_DEBUG, _("adding directory watch for \"%s\"\n"), path);
00444 if ((pw->watch_id = inotify_add_watch(inotify_fd, path, INOTIFY_EVENTS)) < 0) {
00445 error = errno;
00446 log_msg(LOG_ERROR, _("could not add inotify watch for \"%s\" (%s)\n"),
00447 path, error_string(error));
00448 free_path_watch(&pw);
00449 return error;
00450 }
00451 log_msg(LOG_DEBUG, _("newly watching [%d] \"%s\"\n"), pw->watch_id, path);
00452
00453 *pw_return = pw;
00454 return SUCCESS;
00455 }
00456
00457
00458
00459
00460
00461
00462
00463 static int destroy_path_watch(struct path_watch_t **ppw)
00464 {
00465 struct path_watch_t *pw = *ppw;
00466 int error;
00467
00468 log_msg(LOG_DEBUG, _("destroying watch for [%d] \"%s\"\n"), pw->watch_id, pw->path);
00469 inotify_rm_watch(inotify_fd, pw->watch_id);
00470 if ((error = untrack_path_watch(pw)) != SUCCESS) {
00471 log_msg(LOG_ERROR, _("Could not untrack_path_watch for \"%s\" (%s)\n"),
00472 pw->path, error_string(error));
00473 }
00474 free_path_watch(ppw);
00475 return SUCCESS;
00476 }
00477
00478
00479
00480
00481
00482
00483
00484 static int destroy_path_watch_if_empty(struct path_watch_t **ppw)
00485 {
00486 struct path_watch_t *pw = *ppw;
00487 int error;
00488
00489 error = SUCCESS;
00490 if (hash_count(pw->descendant_path_table) == 0) {
00491 log_msg(LOG_DEBUG, _("destroying empty watch for [%d] \"%s\"\n"), pw->watch_id, pw->path);
00492 if ((error = destroy_path_watch(ppw)) != SUCCESS) {
00493 log_msg(LOG_ERROR, _("destroy_path_watch() failed (%s)\n"), error_string(error));
00494 }
00495 }
00496 return error;
00497 }
00498
00499
00500
00501
00502
00503
00504 static int add_path_to_target_list(const char *path)
00505 {
00506 int error;
00507 hash_key_t key;
00508 hash_value_t value;
00509
00510 key.type = HASH_KEY_STRING;
00511 key.str = path;
00512 value.type = HASH_VALUE_UNDEF;
00513
00514 if ((error = hash_enter(target_table, &key, &value)) != HASH_SUCCESS) {
00515 log_msg(LOG_ERROR, _("cannot add to target table \"%s\" (%s)\n"),
00516 path, error_string(error));
00517 return error;
00518 }
00519 return SUCCESS;
00520 }
00521
00522
00523
00524
00525
00526
00527 static int remove_path_from_target_list(const char *path)
00528 {
00529 int error;
00530 hash_key_t key;
00531
00532 key.type = HASH_KEY_STRING;
00533 key.str = path;
00534
00535 if ((error = hash_delete(target_table, &key)) != HASH_SUCCESS) {
00536 log_msg(LOG_ERROR, _("cannot remove from target table \"%s\" (%s)\n"),
00537 path, error_string(error));
00538 return error;
00539 }
00540 return SUCCESS;
00541 }
00542
00543
00544
00545
00546
00547
00548 static int track_path_watch(struct path_watch_t *pw)
00549 {
00550 int error = SUCCESS;
00551 hash_key_t key;
00552 hash_value_t value;
00553
00554 key.type = HASH_KEY_STRING;
00555 key.str = pw->path;
00556 value.type = HASH_VALUE_PTR;
00557 value.ptr = pw;
00558
00559 if ((error = hash_enter(watch_path_table, &key, &value)) != HASH_SUCCESS) {
00560 log_msg(LOG_ERROR, _("cannot add to pathname table \"%s\" (%s)\n"),
00561 pw->path, error_string(error));
00562 return error;
00563 }
00564
00565 key.type = HASH_KEY_ULONG;
00566 key.ul = pw->watch_id;
00567
00568 if ((error = hash_enter(watch_id_table, &key, &value)) != HASH_SUCCESS) {
00569 log_msg(LOG_ERROR, _("cannot add to watch id table %d (%s)\n"),
00570 pw->watch_id, error_string(error));
00571 return error;
00572 }
00573
00574 if (debug > 1) log_watch_path_table("after inserting \"%s\"", pw->path);
00575 return SUCCESS;
00576 }
00577
00578
00579
00580
00581
00582
00583 static int untrack_path_watch(struct path_watch_t *pw)
00584 {
00585 int error = SUCCESS;
00586 hash_key_t key;
00587
00588 if (!pw) {
00589 error = INOTIFY_WATCH_ERROR_NULL_WATCH;
00590 log_msg(LOG_ERROR, _("NULL path_watch (%s)\n"), error_string(error));
00591 return error;
00592 }
00593
00594 key.type = HASH_KEY_STRING;
00595 key.str = pw->path;
00596
00597 if ((error = hash_delete(watch_path_table, &key)) != HASH_SUCCESS) {
00598 log_msg(LOG_ERROR, _("cannot delete from pathname table \"%s\" (%s)\n"),
00599 pw->path, error_string(error));
00600 return error;
00601 }
00602
00603 key.type = HASH_KEY_ULONG;
00604 key.ul = pw->watch_id;
00605
00606 if ((error = hash_delete(watch_id_table, &key) != HASH_SUCCESS)) {
00607 log_msg(LOG_ERROR, _("cannot delete from watch id table \"%s\" (%d)\n"),
00608 pw->watch_id, error_string(error));
00609 return error;
00610 }
00611
00612 if (debug > 1) log_watch_path_table("after deleting \"%s\"", pw->path);
00613 return SUCCESS;
00614 }
00615
00616
00617
00618
00619
00620
00621
00622
00623
00624
00625
00626 static void lost_rename_callback(struct inotify_cookie_cache_entry_t *move)
00627 {
00628 int error;
00629
00630 if (lwatch_event_callback) {
00631 struct lwatch_event_t event;
00632
00633 event.event_type = LWATCH_EVENT_RENAME;
00634 if ((error = copy_path(event.rename.prev_path, move->prev_path, sizeof(event.rename.prev_path))) != SUCCESS) {
00635 log_msg(LOG_ERROR, _("failed to copy path \"%s\" (%s)\n"), move->prev_path, error_string(error));
00636 }
00637 if ((error = copy_path(event.rename.new_path, move->new_path, sizeof(event.rename.new_path))) != SUCCESS) {
00638 log_msg(LOG_ERROR, _("failed to copy path \"%s\" (%s)\n"), move->new_path, error_string(error));
00639 }
00640 lwatch_event_callback(&event);
00641 }
00642 }
00643
00644
00645
00646
00647
00648
00649
00650
00651
00652
00653
00654
00655
00656
00657
00658
00659
00660
00661
00662
00663
00664
00665
00666
00667 static int process_event(struct inotify_event *event)
00668 {
00669 int error;
00670 struct path_watch_t *pw;
00671 char event_path[PATH_MAX];
00672
00673
00674 if (!(pw = find_watch_by_watch_id(event->wd))) {
00675 log_msg(LOG_ERROR, _("could not look up watch id %d\n"), event->wd);
00676 return INOTIFY_WATCH_ERROR_WATCH_ID_NOT_FOUND;
00677 }
00678
00679
00680 if (event->len) {
00681 if ((error = path_concat(event_path, sizeof(event_path), pw->path, event->name)) != SUCCESS) {
00682 log_msg(LOG_ERROR, _("failed to concatenate path \"%s\" + \"%s\" (%s)\n"),
00683 pw->path, event->name, error_string(error));
00684 return error;
00685 }
00686 } else {
00687 if ((error = copy_path(event_path, pw->path, sizeof(event_path))) != SUCCESS) {
00688 log_msg(LOG_ERROR, _("failed to copy path \"%s\" (%s)\n"), pw->path, error_string(error));
00689 return error;
00690 }
00691 }
00692
00693 log_msg(LOG_DEBUG, _("wd=%d mask=[%s] cookie=%u len=%u name=\"%s\" path=\"%s\"\n"),
00694 event->wd, inotify_mask_string(event->mask, ","),
00695 event->cookie, event->len, event->len ? event->name : NULL,
00696 event->len ? event_path : pw->path);
00697
00698
00699
00700
00701
00702
00703
00704 if (debug) {
00705 char *fw_string = path_watch_string(pw);
00706
00707 log_msg(LOG_DEBUG, _("[%s] pw=%s\n"),
00708 inotify_mask_string(event->mask, ","), fw_string);
00709
00710 log_watch_path_table("On entry to: %s", __FUNCTION__);
00711 log_target_table("On entry to: %s", __FUNCTION__);
00712 free(fw_string);
00713 }
00714
00715
00716
00717
00718 if (event->mask & IN_MOVE) {
00719 struct inotify_cookie_cache_entry_t *move;
00720
00721 if ((move = inotify_cookie_cache_operator(event, event_path))) {
00722 log_msg(LOG_DEBUG, _("renamed \"%s\" --> \"%s\"\n"),
00723 move->prev_path, move->new_path);
00724
00725 if (is_watch_target(move->prev_path) || is_watch_target(move->new_path)) {
00726 if (lwatch_event_callback) {
00727 struct lwatch_event_t lw_event;
00728
00729 lw_event.event_type = LWATCH_EVENT_RENAME;
00730 if ((error = copy_path(lw_event.rename.prev_path, move->prev_path,
00731 sizeof(lw_event.rename.prev_path))) != SUCCESS) {
00732 log_msg(LOG_ERROR, _("failed to copy path \"%s\" (%s)\n"),
00733 move->prev_path, error_string(error));
00734 return error;
00735 }
00736 if ((error = copy_path(lw_event.rename.new_path, move->new_path,
00737 sizeof(lw_event.rename.new_path))) != SUCCESS) {
00738 log_msg(LOG_ERROR, _("failed to copy path \"%s\" (%s)\n"),
00739 move->new_path, error_string(error));
00740 return error;
00741 }
00742 lwatch_event_callback(&lw_event);
00743 }
00744 }
00745 }
00746
00747
00748
00749 } else if (event->mask & IN_CREATE) {
00750 char *new_path = event_path;
00751
00752 if (is_path_an_ancestor_of_any_watch_descendants(pw, new_path)) {
00753 log_msg(LOG_DEBUG, _("new path %s \"%s\" is a watch target\n"),
00754 event->mask & IN_ISDIR ? "directory":"file", new_path);
00755
00756 if ((error = relocate_watches(pw)) != SUCCESS) {
00757 log_msg(LOG_ERROR, _("could not relocate_watches for \"%s\" (%s)\n"),
00758 new_path, error_string(error));
00759 }
00760
00761 if (lwatch_event_callback) {
00762 struct lwatch_event_t lw_event;
00763
00764 lw_event.event_type = LWATCH_EVENT_CREATE;
00765 if ((error = copy_path(lw_event.path, new_path, sizeof(lw_event.path))) != SUCCESS) {
00766 log_msg(LOG_ERROR, _("failed to copy path \"%s\" (%s)\n"), new_path, error_string(error));
00767 return error;
00768 }
00769 lwatch_event_callback(&lw_event);
00770 }
00771 } else {
00772 log_msg(LOG_DEBUG, _("new path %s \"%s\" is not within a watch target\n"),
00773 event->mask & IN_ISDIR ? "directory":"file", new_path);
00774 }
00775
00776
00777
00778 } else if (event->mask & IN_MODIFY) {
00779 char *modified_path = event_path;
00780
00781 log_msg(LOG_DEBUG, _("modified \"%s\"\n"), modified_path);
00782
00783 if (is_watch_target(modified_path)) {
00784 log_msg(LOG_DEBUG, _("watched file is modified \"%s\"\n"), modified_path);
00785 if (lwatch_event_callback) {
00786 struct lwatch_event_t lw_event;
00787
00788 lw_event.event_type = LWATCH_EVENT_MODIFY;
00789 if ((error = copy_path(lw_event.path, modified_path, sizeof(lw_event.path))) != SUCCESS) {
00790 log_msg(LOG_ERROR, _("failed to copy path \"%s\" (%s)\n"), modified_path, error_string(error));
00791 return error;
00792 }
00793 lwatch_event_callback(&lw_event);
00794 }
00795 }
00796
00797
00798
00799 } else if (event->mask & IN_OPEN) {
00800 char *opened_path = event_path;
00801
00802 if (is_watch_target(opened_path)) {
00803 log_msg(LOG_DEBUG, _("opened watched \"%s\"\n"), opened_path);
00804 if (lwatch_event_callback) {
00805 struct lwatch_event_t lw_event;
00806
00807 lw_event.event_type = LWATCH_EVENT_OPEN;
00808 if ((error = copy_path(lw_event.path, opened_path, sizeof(lw_event.path))) != SUCCESS) {
00809 log_msg(LOG_ERROR, _("failed to copy path \"%s\" (%s)\n"), opened_path, error_string(error));
00810 return error;
00811 }
00812 lwatch_event_callback(&lw_event);
00813 }
00814 } else {
00815 log_msg(LOG_DEBUG, _("opened unwatched \"%s\"\n"), opened_path);
00816 }
00817
00818
00819
00820 } else if (event->mask & IN_CLOSE_WRITE) {
00821 char *closed_path = event_path;
00822
00823 if (is_watch_target(closed_path)) {
00824 log_msg(LOG_DEBUG, _("closed watched \"%s\"\n"), closed_path);
00825 if (lwatch_event_callback) {
00826 struct lwatch_event_t lw_event;
00827
00828 lw_event.event_type = LWATCH_EVENT_CLOSE;
00829 if ((error = copy_path(lw_event.path, closed_path, sizeof(lw_event.path))) != SUCCESS) {
00830 log_msg(LOG_ERROR, _("failed to copy path \"%s\" (%s)\n"), closed_path, error_string(error));
00831 return error;
00832 }
00833 lwatch_event_callback(&lw_event);
00834 }
00835 } else {
00836 log_msg(LOG_DEBUG, _("closed unwatched \"%s\"\n"), closed_path);
00837 }
00838
00839
00840
00841 } else if (event->mask & IN_DELETE) {
00842 char *deleted_path = event_path;
00843
00844 if (is_watch_target(deleted_path)) {
00845 log_msg(LOG_DEBUG, _("deleted watched \"%s\"\n"), deleted_path);
00846 if (lwatch_event_callback) {
00847 struct lwatch_event_t lw_event;
00848
00849 lw_event.event_type = LWATCH_EVENT_DELETE;
00850 if ((error = copy_path(lw_event.path, deleted_path, sizeof(lw_event.path))) != SUCCESS) {
00851 log_msg(LOG_ERROR, _("failed to copy path \"%s\" (%s)\n"), deleted_path, error_string(error));
00852 return error;
00853 }
00854 lwatch_event_callback(&lw_event);
00855 }
00856 } else {
00857 log_msg(LOG_DEBUG, _("deleted unwatched \"%s\"\n"), deleted_path);
00858 }
00859
00860
00861
00862
00863 } else if (event->mask & IN_DELETE_SELF) {
00864 char deleted_path[PATH_MAX];
00865
00866 if ((error = copy_path(deleted_path, pw->path, sizeof(deleted_path))) != SUCCESS) {
00867 log_msg(LOG_ERROR, _("failed to copy path \"%s\" (%s)\n"), pw->path, error_string(error));
00868 return error;
00869 }
00870 if ((error = relocate_watches(pw)) != SUCCESS) {
00871 log_msg(LOG_ERROR, _("IN_DELETE_SELF could not relocate_watches for \"%s\" (%s)\n"),
00872 deleted_path, error_string(error));
00873 }
00874
00875
00876
00877 } else if (event->mask & IN_IGNORED) {
00878 if ((error = untrack_path_watch(pw)) != SUCCESS) {
00879 log_msg(LOG_ERROR, _("IN_IGNORED could not untrack_path_watch for \"%s\" (%s)\n"),
00880 pw->path, error_string(error));
00881 }
00882 if ((error = free_path_watch(&pw)) != SUCCESS) {
00883 log_msg(LOG_ERROR, _("IN_IGNORED could not free_path_watch (%s)\n"), error_string(error));
00884 }
00885
00886 }
00887 return SUCCESS;
00888 }
00889
00890
00891
00892
00893
00894
00895
00896
00897
00898
00899
00900
00901
00902
00903
00904
00905
00906
00907
00908
00909
00910
00911
00912
00913
00914
00915
00916
00917
00918
00919
00920
00921
00922
00923
00924 static int relocate_watches(struct path_watch_t *pw)
00925 {
00926 int error;
00927 hash_key_t *descendant, *descendant_keys;
00928 unsigned long i, count;
00929 char existing_directory_ancestor[PATH_MAX];
00930 char descendant_path[PATH_MAX];
00931
00932 log_msg(LOG_DEBUG, _("relocate_watches for \"%s\"\n"), pw->path);
00933
00934
00935
00936
00937
00938 if ((error = hash_keys(pw->descendant_path_table, &count, &descendant_keys)) != HASH_SUCCESS) {
00939 log_msg(LOG_ERROR, _("could not get descendant_path_table key list in \"%s\" (%s)\n"),
00940 pw->path, error_string(error));
00941 return error;
00942 }
00943
00944
00945 for (i = 0, descendant = descendant_keys; i < count; i++, descendant++) {
00946 if ((error = copy_path(descendant_path, descendant->str, sizeof(descendant_path))) != SUCCESS) {
00947 log_msg(LOG_ERROR, _("failed to copy path \"%s\" (%s)\n"), descendant->str, error_string(error));
00948 continue;
00949 }
00950
00951 if ((error = find_existing_directory_ancestor(existing_directory_ancestor,
00952 sizeof(existing_directory_ancestor),
00953 descendant_path) != SUCCESS)) {
00954 log_msg(LOG_ERROR, _("failed find_existing_directory_ancestor \"%s\" (%s)\n"),
00955 descendant_path, error_string(error));
00956 continue;
00957 }
00958
00959
00960
00961
00962
00963 if (strcmp(descendant_path, existing_directory_ancestor) != 0) {
00964 log_msg(LOG_VERBOSE_DEBUG, _("existing watch \"%s\" not equal to new watch \"%s\"\n"),
00965 descendant_path, existing_directory_ancestor);
00966
00967 if ((error = hash_delete(pw->descendant_path_table, descendant)) != HASH_SUCCESS) {
00968 log_msg(LOG_ERROR, _("cannot delete from descendant_path_table \"%s\" (%s)\n"),
00969 descendant_path, error_string(error));
00970 continue;
00971 }
00972
00973
00974 if ((error = inotify_start_monitoring(descendant_path)) != SUCCESS) {
00975 log_msg(LOG_ERROR, _("cannot inotify_start_monitoring for \"%s\" (%s)\n"),
00976 descendant_path, error_string(error));
00977 continue;
00978 }
00979 }
00980 }
00981 free(descendant_keys);
00982
00983
00984
00985
00986
00987
00988 if ((error = destroy_path_watch_if_empty(&pw)) != SUCCESS) {
00989 log_msg(LOG_ERROR, _("destroy_path_watch_if_empty failed (%s)\n"),
00990 error_string(error));
00991 }
00992
00993 return SUCCESS;
00994 }
00995
00996
00997
00998
00999
01000
01001
01002
01003
01004
01005
01006
01007
01008
01009
01010
01011
01012
01013
01014
01015
01016
01017 static struct inotify_cookie_cache_entry_t *inotify_cookie_cache_operator(struct inotify_event *event, const char *path)
01018 {
01019 int error;
01020 int i;
01021 struct inotify_cookie_cache_entry_t *entry;
01022 static struct inotify_cookie_cache_entry_t result;
01023
01024
01025 for (i = 0; i < n_cookies; i++) {
01026 if (inotify_cookie_cache[i].cookie == event->cookie) {
01027 entry = &inotify_cookie_cache[i];
01028 if (event->mask & IN_MOVED_FROM) {
01029 entry->prev_watch_id = event->wd;
01030 if ((error = copy_path(entry->prev_path, path, sizeof(entry->prev_path))) != SUCCESS) {
01031 log_msg(LOG_ERROR, _("failed to copy path \"%s\" (%s)\n"), path, error_string(error));
01032 }
01033 } else if (event->mask & IN_MOVED_TO) {
01034 entry->new_watch_id = event->wd;
01035 if ((error = copy_path(entry->new_path, path, sizeof(entry->new_path))) != SUCCESS) {
01036 log_msg(LOG_ERROR, _("failed to copy path \"%s\" (%s)\n"), path, error_string(error));
01037 }
01038 }
01039 result = *entry;
01040
01041
01042 for (i++; i < n_cookies; i++) {
01043 inotify_cookie_cache[i - 1] = inotify_cookie_cache[i];
01044 }
01045 n_cookies--;
01046 return &result;
01047 }
01048 }
01049
01050 if (n_cookies == INOTIFY_COOKIE_CACHE_SIZE) {
01051
01052
01053 if (inotify_lost_cookie_callback)
01054 inotify_lost_cookie_callback(&inotify_cookie_cache[0]);
01055 n_cookies--;
01056 for (i = 0; i < n_cookies; i++) {
01057 inotify_cookie_cache[i] = inotify_cookie_cache[i + 1];
01058 }
01059 }
01060 entry = &inotify_cookie_cache[n_cookies];
01061 entry->cookie = event->cookie;
01062 if (event->mask & IN_MOVED_FROM) {
01063 entry->prev_watch_id = event->wd;
01064 entry->new_watch_id = -1;
01065 if ((error = copy_path(entry->prev_path, path, sizeof(entry->prev_path))) != SUCCESS) {
01066 log_msg(LOG_ERROR, _("failed to copy path \"%s\" (%s)\n"), path, error_string(error));
01067 }
01068 entry->new_path[0] = 0;
01069 } else if (event->mask & IN_MOVED_TO) {
01070 entry->prev_watch_id = -1;
01071 entry->new_watch_id = event->wd;
01072 entry->prev_path[0] = 0;
01073 if ((error = copy_path(entry->new_path, path, sizeof(entry->new_path))) != SUCCESS) {
01074 log_msg(LOG_ERROR, _("failed to copy path \"%s\" (%s)\n"), path, error_string(error));
01075 }
01076 }
01077 n_cookies++;
01078 return NULL;
01079 }
01080
01081
01082
01083
01084
01085
01086
01087
01088
01089
01090
01091
01092
01093
01094
01095 static void flush_inotify_cookie_cache(void)
01096 {
01097 int i;
01098 struct inotify_cookie_cache_entry_t *entry;
01099
01100 for (i = 0; i < n_cookies; i++) {
01101 entry = &inotify_cookie_cache[i];
01102 if (inotify_lost_cookie_callback)
01103 inotify_lost_cookie_callback(entry);
01104 }
01105 n_cookies = 0;
01106 }
01107
01108
01109
01110
01111
01112
01113
01114 static void log_watch_path_table(const char *fmt, ...)
01115 {
01116 char buf[1024];
01117 va_list ap;
01118 char *watch_path_table_str = watch_path_table_string(watch_path_table);
01119
01120 va_start(ap, fmt);
01121 if (fmt) {
01122 vsnprintf(buf, sizeof(buf), fmt, ap);
01123 } else {
01124 *buf = 0;
01125 }
01126
01127 log_msg(LOG_DEBUG, _("pathname table: %s\n%s\n"), buf, watch_path_table_str);
01128 free(watch_path_table_str);
01129 }
01130
01131
01132
01133
01134
01135
01136
01137 static void log_target_table(const char *fmt, ...)
01138 {
01139 char buf[1024];
01140 va_list ap;
01141 char *targets = keys_string(target_table, " ", "\n");
01142
01143 va_start(ap, fmt);
01144 if (fmt) {
01145 vsnprintf(buf, sizeof(buf), fmt, ap);
01146 } else {
01147 *buf = 0;
01148 }
01149
01150 log_msg(LOG_DEBUG, _("target table: %s\n%s\n"), buf, targets);
01151 free(targets);
01152 }
01153
01154
01155
01156
01157
01158
01159
01160
01161
01162 int inotify_watch_init()
01163 {
01164 int error;
01165
01166 inotify_lost_cookie_callback = lost_rename_callback;
01167
01168 if ((inotify_fd = inotify_init()) < 0) {
01169 error = errno;
01170 log_msg(LOG_ERROR, _("inotify_init() failed (%s)\n"), error_string(error));
01171 return error;
01172 }
01173
01174
01175 if ((error = hash_create(INITIAL_HASH_TABLE_SIZE, &watch_path_table, NULL)) != HASH_SUCCESS) {
01176 log_msg(LOG_ERROR, _("inotify_watch_init: cannot create pathname hash table\n"));
01177 return error;
01178 }
01179
01180 if ((error = hash_create(INITIAL_HASH_TABLE_SIZE, &watch_id_table, NULL)) != HASH_SUCCESS) {
01181 log_msg(LOG_ERROR, _("inotify_watch_init: cannot create watch id hash table\n"));
01182 return error;
01183 }
01184
01185 if ((error = hash_create(INITIAL_HASH_TABLE_SIZE, &target_table, NULL)) != HASH_SUCCESS) {
01186 log_msg(LOG_ERROR, _("%s: cannot create target hash table\n"));
01187 return error;
01188 }
01189
01190 return SUCCESS;
01191 }
01192
01193
01194
01195
01196
01197 int inotify_watch_fini()
01198 {
01199 unsigned long i, count;
01200 hash_entry_t *entries, *entry;
01201 struct path_watch_t *pw;
01202
01203 flush_inotify_cookie_cache();
01204
01205 hash_entries(watch_id_table, &count, &entries);
01206 for (i = 0, entry = entries; i < count; i++, entry++) {
01207 pw = entry->value.ptr;
01208 log_msg(LOG_VERBOSE_DEBUG, _("destroying [%d] \"%s\"\n"), pw->watch_id, pw->path);
01209 destroy_path_watch(&pw);
01210 }
01211 free(entries);
01212
01213 if (hash_count(watch_path_table) != 0) {
01214 log_msg(LOG_ERROR, _("watch_path_table not empty (%d) after removing all watches\n"),
01215 hash_count(watch_path_table));
01216 }
01217
01218 if (hash_count(watch_id_table) != 0) {
01219 log_msg(LOG_ERROR, _("watch_id_table not empty (%d) after removing all watches\n"),
01220 hash_count(watch_id_table));
01221 }
01222
01223 hash_destroy(watch_path_table);
01224 hash_destroy(watch_id_table);
01225 hash_destroy(target_table);
01226 close(inotify_fd);
01227
01228 return SUCCESS;
01229 }
01230
01231
01232
01233
01234
01235
01236
01237
01238
01239
01240
01241
01242
01243
01244
01245 bool is_watch_target(const char *path)
01246 {
01247 hash_key_t key;
01248
01249 key.type = HASH_KEY_STRING;
01250 key.str = path;
01251
01252 return hash_has_key(target_table, &key);
01253 }
01254
01255
01256
01257
01258
01259
01260
01261
01262
01263
01264
01265
01266
01267 bool is_path_watched(const char* path)
01268 {
01269 hash_key_t key;
01270
01271 key.type = HASH_KEY_STRING;
01272 key.str = path;
01273
01274 return hash_has_key(watch_path_table, &key);
01275 }
01276
01277
01278
01279
01280
01281
01282
01283
01284
01285
01286
01287 const char *inotify_watch_error_string(int error)
01288 {
01289 switch(error) {
01290 case SUCCESS: return _("success");
01291 case INOTIFY_WATCH_ERROR_NULL_WATCH: return _("watch invalid, was NULL");
01292 case INOTIFY_WATCH_ERROR_WATCH_ID_NOT_FOUND: return _("could not look up watch id");
01293 case INOTIFY_WATCH_ALREADY_WATCHING: return _("already being watched");
01294 case INOTIFY_WATCH_NOT_WATCHED: return _("not being watched");
01295 }
01296 return NULL;
01297 }
01298
01299
01300
01301
01302
01303
01304
01305 char *inotify_mask_string(unsigned int mask, const char *separator)
01306 {
01307 static __thread char buf[1024];
01308 char *p, *buf_end;
01309 size_t separator_len;
01310 unsigned int unknown;
01311
01312 p = buf;
01313 buf_end = &buf[sizeof(buf)];
01314 separator_len = strlen(separator);
01315 *p = 0;
01316
01317
01318 unknown = mask & ~(IN_ACCESS | IN_MODIFY | IN_ATTRIB | IN_CLOSE_WRITE |
01319 IN_CLOSE_NOWRITE | IN_OPEN | IN_MOVED_FROM | IN_MOVED_TO |
01320 IN_CREATE | IN_DELETE | IN_DELETE_SELF | IN_MOVE_SELF |
01321 IN_UNMOUNT | IN_Q_OVERFLOW | IN_IGNORED | IN_ONLYDIR |
01322 IN_DONT_FOLLOW | IN_MASK_ADD | IN_ISDIR | IN_ONESHOT);
01323
01324 if (unknown) p += snprintf(p, buf_end - p, "unknown bits=0x%x%s", unknown, separator);
01325 if (p >= buf_end) goto fail;
01326
01327 if (mask & IN_ACCESS) p += snprintf(p, buf_end - p, "ACCESS%s", separator);
01328 if (p >= buf_end) goto fail;
01329 if (mask & IN_MODIFY) p += snprintf(p, buf_end - p, "MODIFY%s", separator);
01330 if (p >= buf_end) goto fail;
01331 if (mask & IN_ATTRIB) p += snprintf(p, buf_end - p, "ATTRIB%s", separator);
01332 if (p >= buf_end) goto fail;
01333 if (mask & IN_CLOSE_WRITE) p += snprintf(p, buf_end - p, "CLOSE_WRITE%s", separator);
01334 if (p >= buf_end) goto fail;
01335 if (mask & IN_CLOSE_NOWRITE) p += snprintf(p, buf_end - p, "CLOSE_NOWRITE%s", separator);
01336 if (p >= buf_end) goto fail;
01337 if (mask & IN_OPEN) p += snprintf(p, buf_end - p, "OPEN%s", separator);
01338 if (p >= buf_end) goto fail;
01339 if (mask & IN_MOVED_FROM) p += snprintf(p, buf_end - p, "MOVED_FROM%s", separator);
01340 if (p >= buf_end) goto fail;
01341 if (mask & IN_MOVED_TO) p += snprintf(p, buf_end - p, "MOVED_TO%s", separator);
01342 if (p >= buf_end) goto fail;
01343 if (mask & IN_CREATE) p += snprintf(p, buf_end - p, "CREATE%s", separator);
01344 if (p >= buf_end) goto fail;
01345 if (mask & IN_DELETE) p += snprintf(p, buf_end - p, "DELETE%s", separator);
01346 if (p >= buf_end) goto fail;
01347 if (mask & IN_DELETE_SELF) p += snprintf(p, buf_end - p, "DELETE_SELF%s", separator);
01348 if (p >= buf_end) goto fail;
01349 if (mask & IN_MOVE_SELF) p += snprintf(p, buf_end - p, "MOVE_SELF%s", separator);
01350 if (p >= buf_end) goto fail;
01351 if (mask & IN_UNMOUNT) p += snprintf(p, buf_end - p, "UNMOUNT%s", separator);
01352 if (p >= buf_end) goto fail;
01353 if (mask & IN_Q_OVERFLOW) p += snprintf(p, buf_end - p, "Q_OVERFLOW%s", separator);
01354 if (p >= buf_end) goto fail;
01355 if (mask & IN_IGNORED) p += snprintf(p, buf_end - p, "IGNORED%s", separator);
01356 if (p >= buf_end) goto fail;
01357 if (mask & IN_ONLYDIR) p += snprintf(p, buf_end - p, "ONLYDIR%s", separator);
01358 if (p >= buf_end) goto fail;
01359 if (mask & IN_DONT_FOLLOW) p += snprintf(p, buf_end - p, "DONT_FOLLOW%s", separator);
01360 if (p >= buf_end) goto fail;
01361 if (mask & IN_MASK_ADD) p += snprintf(p, buf_end - p, "MASK_ADD%s", separator);
01362 if (p >= buf_end) goto fail;
01363 if (mask & IN_ISDIR) p += snprintf(p, buf_end - p, "ISDIR%s", separator);
01364 if (p >= buf_end) goto fail;
01365 if (mask & IN_ONESHOT) p += snprintf(p, buf_end - p, "ONESHOT%s", separator);
01366 if (p >= buf_end) goto fail;
01367
01368 if (p > buf) p[-separator_len] = 0;
01369
01370 return buf;
01371 fail:
01372
01373 buf_end[-1] = 0;
01374 return buf;
01375 }
01376
01377
01378
01379
01380
01381
01382 char *path_watch_string(struct path_watch_t *pw)
01383 {
01384 char *files;
01385 char *fw_string;
01386 int len;
01387
01388 if (!pw) return strdup("(null)");
01389
01390 files = keys_string(pw->descendant_path_table, " ", "\n");
01391
01392 len = asprintf(&fw_string, "[%d] \"%s\" n_files=%ld%s%s",
01393 pw->watch_id, pw->path,
01394 hash_count(pw->descendant_path_table),
01395 hash_count(pw->descendant_path_table) ? "\n" : "", files);
01396
01397 free(files);
01398 return fw_string;
01399 }
01400
01401
01402
01403
01404
01405
01406
01407 struct path_watch_t *find_watch_by_path(const char* path)
01408 {
01409 int error;
01410 hash_key_t key;
01411 hash_value_t value;
01412 struct path_watch_t *pw;
01413
01414 key.type = HASH_KEY_STRING;
01415 key.str = path;
01416
01417 if ((error = hash_lookup(watch_path_table, &key, &value)) != HASH_SUCCESS) {
01418 log_msg(LOG_VERBOSE_DEBUG, _("cannot find pathname \"%s\"\n"), path);
01419 return NULL;
01420 }
01421
01422 pw = (struct path_watch_t *) value.ptr;
01423 return pw;
01424 }
01425
01426
01427
01428
01429
01430
01431 struct path_watch_t *find_watch_by_watch_id(int watch_id)
01432 {
01433 int error;
01434 hash_key_t key;
01435 hash_value_t value;
01436 struct path_watch_t *pw;
01437
01438 key.type = HASH_KEY_ULONG;
01439 key.ul = watch_id;
01440
01441 if ((error = hash_lookup(watch_id_table, &key, &value)) != HASH_SUCCESS) {
01442 log_msg(LOG_DEBUG, _("cannot find watch_id %d\n"), watch_id);
01443 return NULL;
01444 }
01445
01446 pw = (struct path_watch_t *) value.ptr;
01447 return pw;
01448 }
01449
01450
01451
01452
01453
01454
01455 int inotify_start_monitoring(const char *path_arg)
01456 {
01457 int error;
01458 char path[PATH_MAX];
01459 hash_key_t key;
01460 hash_value_t value;
01461 char existing_directory_ancestor[PATH_MAX];
01462 struct path_watch_t *pw;
01463
01464
01465 if ((error = make_normalized_absolute_path(path, sizeof(path), path_arg)) != SUCCESS) {
01466 log_msg(LOG_ERROR, _("failed make_normalized_absolute_path from \"%s\" (%s)\n"),
01467 path_arg, error_string(error));
01468 return error;
01469 }
01470
01471
01472 if (is_watch_target(path)) {
01473 error = INOTIFY_WATCH_ALREADY_WATCHING;
01474 log_msg(LOG_ERROR, _("path is already being watched \"%s\"\n"), path);
01475 return error;
01476 }
01477
01478 log_msg(LOG_INFO, _("watching target \"%s\"\n"), path);
01479
01480
01481
01482
01483
01484
01485
01486
01487
01488
01489
01490
01491
01492
01493
01494
01495
01496
01497
01498
01499 if ((error = find_existing_directory_ancestor(existing_directory_ancestor,
01500 sizeof(existing_directory_ancestor),
01501 path) != SUCCESS)) {
01502 log_msg(LOG_ERROR, _("failed find_existing_directory_ancestor \"%s\" (%s)\n"),
01503 path, error_string(error));
01504 return error;
01505 }
01506
01507
01508
01509
01510
01511
01512 if ((pw = find_watch_by_path(existing_directory_ancestor)) == NULL) {
01513 if ((error = new_path_watch(existing_directory_ancestor, &pw)) != SUCCESS) {
01514 log_msg(LOG_ERROR, _("could not allocate new watch for \"%s\" (%s)\n"),
01515 existing_directory_ancestor, error_string(error));
01516 return error;
01517 }
01518 }
01519
01520
01521
01522
01523 if ((error = add_path_to_target_list(path)) != SUCCESS) {
01524 log_msg(LOG_ERROR, _("could not insert path into target table \"%s\" (%s)\n"),
01525 pw->path, error_string(error));
01526 inotify_rm_watch(inotify_fd, pw->watch_id);
01527 free_path_watch(&pw);
01528 return error;
01529 }
01530
01531
01532 if ((error = track_path_watch(pw)) != SUCCESS) {
01533 log_msg(LOG_ERROR, _("could not insert watch into tables for \"%s\" (%s)\n"),
01534 pw->path, error_string(error));
01535 inotify_rm_watch(inotify_fd, pw->watch_id);
01536 free_path_watch(&pw);
01537 return error;
01538 }
01539
01540 log_msg(LOG_DEBUG, _("adding path \"%s\" to descendant_path_table for directory \"%s\"\n"),
01541 path, pw->path);
01542
01543
01544 key.type = HASH_KEY_STRING;
01545 key.str = path;
01546 value.type = HASH_VALUE_UNDEF;
01547
01548 if ((error = hash_enter(pw->descendant_path_table, &key, &value)) != HASH_SUCCESS) {
01549 log_msg(LOG_ERROR, _("cannot add path \"%s\" to path_watch_t descendant_path_table \"%s\" (%s)\n"),
01550 path, pw->path, error_string(error));
01551 return error;
01552 }
01553
01554 return SUCCESS;
01555 }
01556
01557
01558
01559
01560
01561
01562 int inotify_stop_monitoring(const char *path_arg)
01563 {
01564 int error;
01565 char path[PATH_MAX];
01566 unsigned long i, count;
01567 hash_key_t key;
01568 hash_entry_t *entries, *entry;
01569 struct path_watch_t *pw;
01570 int path_count;
01571
01572 if ((error = make_normalized_absolute_path(path, sizeof(path), path_arg)) != SUCCESS) {
01573 log_msg(LOG_ERROR, _("failed make_normalized_absolute_path from \"%s\" (%s)\n"),
01574 path_arg, error_string(error));
01575 return error;
01576 }
01577
01578 if (!is_watch_target(path)) {
01579 error = INOTIFY_WATCH_NOT_WATCHED;
01580 log_msg(LOG_ERROR, _("path was not being watched \"%s\"\n"), path);
01581 return error;
01582 }
01583
01584 log_msg(LOG_INFO, _("removing watch target \"%s\"\n"), path);
01585
01586 key.type = HASH_KEY_STRING;
01587 key.str = path;
01588
01589 path_count = 0;
01590 hash_entries(watch_id_table, &count, &entries);
01591 for (i = 0, entry = entries; i < count; i++, entry++) {
01592 pw = entry->value.ptr;
01593
01594 if (hash_has_key(pw->descendant_path_table, &key)) {
01595 path_count++;
01596 if ((error = hash_delete(pw->descendant_path_table, &key)) != HASH_SUCCESS) {
01597 log_msg(LOG_ERROR, _("cannot delete path \"%s\" from path_watch_t descendant_path_table \"%s\" (%s)\n"),
01598 path, pw->path, error_string(error));
01599 }
01600 if ((error = destroy_path_watch_if_empty(&pw)) != SUCCESS) {
01601 log_msg(LOG_ERROR, _("destroy_path_watch_if_empty failed (%s)\n"), error_string(error));
01602 }
01603 }
01604 }
01605 free(entries);
01606
01607 if (path_count != 1) {
01608 log_msg(LOG_ERROR, _("found %d instances of \"%s\" but expected exactly one\n"),
01609 path_count, path);
01610 }
01611
01612 if ((error = remove_path_from_target_list(path)) != SUCCESS) {
01613 log_msg(LOG_ERROR, _("could not remove path from target table \"%s\" (%s)\n"),
01614 path, error_string(error));
01615 }
01616
01617 return error;
01618 }
01619
01620
01621
01622
01623
01624
01625
01626
01627 int get_inotify_fd()
01628 {
01629 return inotify_fd;
01630 }
01631
01632
01633
01634
01635
01636 int read_events()
01637 {
01638 char buf[1024 * (INOTIFY_EVENT_SIZE + 16)];
01639 int len, error, i;
01640 bool done;
01641
01642
01643
01644
01645
01646
01647
01648 done = false;
01649 while (!done) {
01650 len = read (inotify_fd, buf, sizeof(buf));
01651 i = 0;
01652 if (len <= 0) {
01653 error = errno;
01654 if (error == EINTR || error == EAGAIN) {
01655 continue;
01656 }
01657 else if (error == EINVAL || len == 0) {
01658 log_msg(LOG_ERROR, _("failed, buffer too small (%s)\n"),
01659 error_string(error));
01660 return error;
01661 } else {
01662 log_msg(LOG_ERROR, _("failed (%s)\n"),
01663 error_string(error));
01664 return error;
01665 }
01666 }
01667 while (i < len) {
01668 struct inotify_event *event;
01669
01670 event = (struct inotify_event *) &buf[i];
01671
01672 process_event(event);
01673 i += INOTIFY_EVENT_SIZE + event->len;
01674 }
01675 done = true;
01676 }
01677 return SUCCESS;
01678 }
01679
01680
01681
01682
01683
01684
01685 lwatch_event_callback_t register_lwatch_event_callback(lwatch_event_callback_t callback)
01686 {
01687 lwatch_event_callback_t prev_callback = lwatch_event_callback;
01688 lwatch_event_callback = callback;
01689 return prev_callback;
01690 }