source: trunk/src/sh_fInotify.c@ 483

Last change on this file since 483 was 481, checked in by katerina, 9 years ago

Enhancements and fixes for tickets #374, #375, #376, #377, #378, and #379.

File size: 18.4 KB
Line 
1/* SAMHAIN file system integrity testing */
2/* Copyright (C) 2011 Rainer Wichmann */
3/* */
4/* This program is free software; you can redistribute it */
5/* and/or modify */
6/* it under the terms of the GNU General Public License as */
7/* published by */
8/* the Free Software Foundation; either version 2 of the License, or */
9/* (at your option) any later version. */
10/* */
11/* This program is distributed in the hope that it will be useful, */
12/* but WITHOUT ANY WARRANTY; without even the implied warranty of */
13/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
14/* GNU General Public License for more details. */
15/* */
16/* You should have received a copy of the GNU General Public License */
17/* along with this program; if not, write to the Free Software */
18/* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
19
20/***************************************************************************
21 *
22 * This file provides a module for samhain to use inotify for file checking.
23 *
24 */
25
26#include "config_xor.h"
27
28#if (defined(SH_WITH_CLIENT) || defined(SH_STANDALONE))
29
30#include "samhain.h"
31#include "sh_utils.h"
32#include "sh_modules.h"
33#include "sh_pthread.h"
34#include "sh_inotify.h"
35#include "sh_unix.h"
36#include "sh_hash.h"
37#include "sh_dbIO.h"
38#include "sh_files.h"
39#include "sh_ignore.h"
40
41#define FIL__ _("sh_fInotify.c")
42
43sh_watches sh_file_watches = SH_INOTIFY_INITIALIZER;
44
45#if defined(HAVE_SYS_INOTIFY_H)
46
47static sh_watches sh_file_missing = SH_INOTIFY_INITIALIZER;
48
49#include <sys/inotify.h>
50
51/* --- Configuration ------- */
52
53static int ShfInotifyActive = S_FALSE;
54
55static unsigned long ShfInotifyWatches = 0;
56
57static int sh_fInotify_active(const char *s)
58{
59 int value;
60
61 SL_ENTER(_("sh_fInotify_active"));
62 value = sh_util_flagval(s, &ShfInotifyActive);
63 if (value == 0 && ShfInotifyActive != S_FALSE)
64 {
65 sh.flag.inotify |= SH_INOTIFY_USE;
66 sh.flag.inotify |= SH_INOTIFY_DOSCAN;
67 sh.flag.inotify |= SH_INOTIFY_NEEDINIT;
68 }
69 if (value == 0 && ShfInotifyActive == S_FALSE)
70 {
71 sh.flag.inotify = 0;
72 }
73 SL_RETURN((value), _("sh_fInotify_active"));
74}
75
76static int sh_fInotify_watches(const char *s)
77{
78 int retval = -1;
79 char * foo;
80 unsigned long value;
81
82 SL_ENTER(_("sh_fInotify_watches"));
83
84 value = strtoul(s, &foo, 0);
85 if (*foo == '\0')
86 {
87 ShfInotifyWatches = (value > 2147483647) ? 2147483647 /* MAX_INT_32 */: value;
88 retval = 0;
89 }
90 SL_RETURN((retval), _("sh_fInotify_watches"));
91}
92
93
94sh_rconf sh_fInotify_table[] = {
95 {
96 N_("inotifyactive"),
97 sh_fInotify_active,
98 },
99 {
100 N_("inotifywatches"),
101 sh_fInotify_watches,
102 },
103 {
104 NULL,
105 NULL
106 }
107};
108
109/* --- End Configuration --- */
110
111static int sh_fInotify_init_internal(void);
112static int sh_fInotify_process(struct inotify_event * event);
113static int sh_fInotify_report(struct inotify_event * event, char * filename,
114 int class, unsigned long check_flags, int ftype, int rdepth);
115
116int sh_fInotify_init(struct mod_type * arg)
117{
118#ifndef HAVE_PTHREAD
119 (void) arg;
120 return SH_MOD_FAILED;
121#else
122
123 if (ShfInotifyActive == S_FALSE)
124 return SH_MOD_FAILED;
125
126 if (sh.flag.checkSum == SH_CHECK_INIT)
127 return SH_MOD_FAILED;
128
129 if (arg != NULL && arg->initval < 0 &&
130 (sh.flag.isdaemon == S_TRUE || sh.flag.loop == S_TRUE))
131 {
132 /* Init from main thread */
133 SH_INOTIFY_IFUSED( sh.flag.inotify |= SH_INOTIFY_DOSCAN; );
134 SH_INOTIFY_IFUSED( sh.flag.inotify |= SH_INOTIFY_NEEDINIT; );
135
136 if (0 == sh_pthread_create(sh_threaded_module_run, (void *)arg))
137 {
138 return SH_MOD_THREAD;
139 }
140 else
141 {
142 sh.flag.inotify = 0;
143 return SH_MOD_FAILED;
144 }
145 }
146 else if (arg != NULL && arg->initval < 0 &&
147 (sh.flag.isdaemon != S_TRUE && sh.flag.loop != S_TRUE))
148 {
149 sh.flag.inotify = 0;
150 return SH_MOD_FAILED;
151 }
152 else if (arg != NULL && arg->initval == SH_MOD_THREAD &&
153 (sh.flag.isdaemon == S_TRUE || sh.flag.loop == S_TRUE))
154 {
155 /* Reconfigure from main thread */
156 /* sh_fInotify_init_internal(); */
157 SH_INOTIFY_IFUSED( sh.flag.inotify |= SH_INOTIFY_DOSCAN; );
158 SH_INOTIFY_IFUSED( sh.flag.inotify |= SH_INOTIFY_NEEDINIT; );
159 return SH_MOD_THREAD;
160 }
161
162 /* Within thread, init */
163 return sh_fInotify_init_internal();
164#endif
165}
166
167int sh_fInotify_run()
168{
169 ssize_t len = -1;
170 char * buffer;
171 static int count = 0;
172 static int count2 = 0;
173
174 if (ShfInotifyActive == S_FALSE)
175 {
176 return SH_MOD_FAILED;
177 }
178
179 if ( (sh.flag.inotify & SH_INOTIFY_DOSCAN) ||
180 (sh.flag.inotify & SH_INOTIFY_NEEDINIT))
181 {
182 if (0 != sh_fInotify_init_internal())
183 {
184 return SH_MOD_FAILED;
185 }
186 }
187
188 buffer = SH_ALLOC(16384);
189
190 /* Blocking read from inotify file descriptor.
191 */
192 len = sh_inotify_read_timeout(buffer, 16384, 1);
193
194 if (len > 0)
195 {
196 struct inotify_event *event;
197 int i = 0;
198
199 while (i < len)
200 {
201 event = (struct inotify_event *) &(buffer[i]);
202
203 sh_fInotify_process(event);
204
205 i += sizeof (struct inotify_event) + event->len;
206 }
207
208 if ( (sh.flag.inotify & SH_INOTIFY_DOSCAN) ||
209 (sh.flag.inotify & SH_INOTIFY_NEEDINIT))
210 {
211 if (0 != sh_fInotify_init_internal())
212 {
213 SH_FREE(buffer);
214 return SH_MOD_FAILED;
215 }
216 }
217 }
218
219 /* Re-scan 'dormant' list of sh_file_missing.
220 */
221 sh_inotify_recheck_watches (&sh_file_watches, &sh_file_missing);
222
223 ++count;
224 ++count2;
225
226 if (count >= 10)
227 {
228 count = 0; /* Re-expand glob patterns to discover added files. */
229 SH_INOTIFY_IFUSED( sh.flag.inotify |= SH_INOTIFY_INSCAN; );
230 sh_files_check_globFilePatterns();
231 SH_INOTIFY_IFUSED( sh.flag.inotify &= ~SH_INOTIFY_INSCAN; );
232 SH_INOTIFY_IFUSED( sh.flag.inotify |= SH_INOTIFY_NEEDINIT; );
233 }
234
235 if (count2 >= 300)
236 {
237 count2 = 0; /* Update baseline database. */
238 if (sh.flag.checkSum == SH_CHECK_CHECK && sh.flag.update == S_TRUE)
239 sh_dbIO_writeout_update ();
240 }
241
242 SH_FREE(buffer);
243 return 0;
244}
245
246/* We block in the read() call on the inotify descriptor,
247 * so we always run.
248 */
249int sh_fInotify_timer(time_t tcurrent)
250{
251 (void) tcurrent;
252 return 1;
253}
254
255int sh_fInotify_cleanup()
256{
257 sh_inotify_purge_dormant(&sh_file_watches);
258 sh_inotify_remove(&sh_file_watches);
259 sh_inotify_init(&sh_file_watches);
260 return 0;
261}
262
263int sh_fInotify_reconf()
264{
265 sh.flag.inotify = 0;
266
267 ShfInotifyWatches = 0;
268 ShfInotifyActive = 0;
269
270 return sh_fInotify_cleanup();
271}
272
273#define PROC_WATCHES_MAX _("/proc/sys/fs/inotify/max_user_watches")
274
275static void sh_fInotify_set_nwatches()
276{
277 static int fails = 0;
278
279 if (ShfInotifyWatches == 0 || fails == 1)
280 return;
281
282 if (0 == access(PROC_WATCHES_MAX, R_OK|W_OK)) /* flawfinder: ignore */
283 {
284 FILE * fd;
285
286 if (NULL != (fd = fopen(PROC_WATCHES_MAX, "r+")))
287 {
288 char str[128];
289 char * ret;
290 char * ptr;
291 unsigned long wn;
292
293 str[0] = '\0';
294 ret = fgets(str, 128, fd);
295 if (ret && *str != '\0')
296 {
297 wn = strtoul(str, &ptr, 0);
298 if (*ptr == '\0' || *ptr == '\n')
299 {
300 if (wn < ShfInotifyWatches)
301 {
302 sl_snprintf(str, sizeof(str), "%lu\n", ShfInotifyWatches);
303 (void) fseek(fd, 0L, SEEK_SET);
304 fputs(str, fd);
305 }
306 sl_fclose(FIL__, __LINE__, fd);
307 return;
308 }
309 }
310 sl_fclose(FIL__, __LINE__, fd);
311 }
312 }
313 SH_MUTEX_LOCK(mutex_thread_nolog);
314 sh_error_handle((-1), FIL__, __LINE__, 0, MSG_E_SUBGEN,
315 _("Cannot set max_user_watches"),
316 _("sh_fInotify_set_nwatches"));
317 SH_MUTEX_UNLOCK(mutex_thread_nolog);
318 fails = 1;
319 return;
320}
321
322/* The watch fd is thread specific. To have it in the fInotify thread,
323 * the main thread writes a list of files/dirs to watch, and here we
324 * now pop files from the list to add watches for them.
325 */
326static int sh_fInotify_init_internal()
327{
328 char * filename;
329 int class;
330 int type;
331 int rdepth;
332 unsigned long check_flags;
333 int retval;
334 int errnum;
335
336 if (ShfInotifyActive == S_FALSE)
337 return SH_MOD_FAILED;
338
339 /* Wait until file scan is finished.
340 */
341 while((sh.flag.inotify & SH_INOTIFY_DOSCAN) != 0)
342 {
343 retry_msleep(1,0);
344
345 if (ShfInotifyActive == S_FALSE)
346 return SH_MOD_FAILED;
347 }
348
349 sh_fInotify_set_nwatches();
350
351 while (NULL != (filename = sh_inotify_pop_dormant(&sh_file_watches,
352 &class, &check_flags,
353 &type, &rdepth)))
354 {
355 retval = sh_inotify_add_watch(filename, &sh_file_watches, &errnum,
356 class, check_flags, type, rdepth);
357
358 if (retval < 0)
359 {
360 char errbuf[SH_ERRBUF_SIZE];
361
362 sh_error_message(errnum, errbuf, sizeof(errbuf));
363
364 if ((errnum == ENOENT) || (errnum == EEXIST))
365 {
366 /* (1) Did it exist at init ?
367 */
368 if (sh_hash_have_it (filename) >= 0)
369 {
370 /* (2) Do we want to report on it ?
371 */
372 if (S_FALSE == sh_ignore_chk_del(filename))
373 {
374 char * epath = sh_util_safe_name (filename);
375
376 SH_MUTEX_LOCK(mutex_thread_nolog);
377 sh_error_handle( SH_ERR_ALL /* debug */,
378 FIL__, __LINE__, errnum, MSG_E_SUBGPATH,
379 errbuf, _("sh_fInotify_init_internal"), epath);
380 SH_MUTEX_UNLOCK(mutex_thread_nolog);
381 SH_FREE(epath);
382 }
383 }
384 }
385 else
386 {
387 SH_MUTEX_LOCK(mutex_thread_nolog);
388 sh_error_handle((-1), FIL__, __LINE__, errnum, MSG_E_SUBGEN,
389 errbuf, _("sh_fInotify_init_internal"));
390 SH_MUTEX_UNLOCK(mutex_thread_nolog);
391 }
392 }
393 SH_FREE(filename);
394 }
395
396 /* Need this because mod_check() may run after
397 * DOSCAN is finished, hence wouldn't call init().
398 */
399 SH_INOTIFY_IFUSED( sh.flag.inotify &= ~SH_INOTIFY_NEEDINIT; );
400
401 return 0;
402}
403
404static void sh_fInotify_logmask(struct inotify_event * event)
405{
406 char dbgbuf[256];
407
408 sl_strlcpy (dbgbuf, "inotify mask: ", sizeof(dbgbuf));
409
410 if (event->mask & IN_ACCESS) sl_strlcat(dbgbuf, "IN_ACCESS ", sizeof(dbgbuf));
411 if (event->mask & IN_ATTRIB) sl_strlcat(dbgbuf, "IN_ATTRIB ", sizeof(dbgbuf));
412 if (event->mask & IN_CLOSE_WRITE) sl_strlcat(dbgbuf, "IN_CLOSE_WRITE ", sizeof(dbgbuf));
413 if (event->mask & IN_CLOSE_NOWRITE) sl_strlcat(dbgbuf, "IN_CLOSE_NOWRITE ", sizeof(dbgbuf));
414 if (event->mask & IN_CREATE) sl_strlcat(dbgbuf, "IN_CREATE ", sizeof(dbgbuf));
415 if (event->mask & IN_DELETE) sl_strlcat(dbgbuf, "IN_DELETE ", sizeof(dbgbuf));
416 if (event->mask & IN_DELETE_SELF) sl_strlcat(dbgbuf, "IN_DELETE_SELF ", sizeof(dbgbuf));
417 if (event->mask & IN_MODIFY) sl_strlcat(dbgbuf, "IN_MODIFY ", sizeof(dbgbuf));
418 if (event->mask & IN_MOVE_SELF) sl_strlcat(dbgbuf, "IN_MOVE_SELF ", sizeof(dbgbuf));
419 if (event->mask & IN_MOVED_FROM) sl_strlcat(dbgbuf, "IN_MOVED_FROM ", sizeof(dbgbuf));
420 if (event->mask & IN_MOVED_TO) sl_strlcat(dbgbuf, "IN_MOVED_TO ", sizeof(dbgbuf));
421 if (event->mask & IN_OPEN) sl_strlcat(dbgbuf, "IN_OPEN ", sizeof(dbgbuf));
422 if (event->mask & IN_IGNORED) sl_strlcat(dbgbuf, "IN_IGNORED ", sizeof(dbgbuf));
423 if (event->mask & IN_ISDIR) sl_strlcat(dbgbuf, "IN_ISDIR ", sizeof(dbgbuf));
424 if (event->mask & IN_Q_OVERFLOW) sl_strlcat(dbgbuf, "IN_Q_OVERFLOW ", sizeof(dbgbuf));
425 if (event->mask & IN_UNMOUNT) sl_strlcat(dbgbuf, "IN_UNMOUNT ", sizeof(dbgbuf));
426
427 SH_MUTEX_LOCK(mutex_thread_nolog);
428 sh_error_handle(SH_ERR_ALL, FIL__, __LINE__, 0, MSG_E_SUBGEN,
429 dbgbuf, _("sh_fInotify_process"));
430 SH_MUTEX_UNLOCK(mutex_thread_nolog);
431}
432
433static int sh_fInotify_process(struct inotify_event * event)
434{
435 int class;
436 int ftype;
437 int rdepth;
438 unsigned long check_flags;
439 char * filename;
440 extern int flag_err_debug;
441
442 if (flag_err_debug == S_TRUE)
443 {
444 sh_fInotify_logmask(event);
445 }
446
447 if (event->wd >= 0)
448 {
449 filename = sh_inotify_search_item(&sh_file_watches, event->wd,
450 &class, &check_flags, &ftype, &rdepth);
451
452 if (filename)
453 {
454 sh_fInotify_report(event, filename, class, check_flags, ftype, rdepth);
455 SH_FREE(filename);
456 }
457 else if (sh.flag.inotify & SH_INOTIFY_NEEDINIT)
458 {
459 return 1;
460 }
461 else if ((event->mask & IN_UNMOUNT) == 0 && (event->mask & IN_IGNORED) == 0)
462 {
463 /* Remove watch ? Seems reasonable. */
464 sh_inotify_rm_watch(NULL, NULL, event->wd);
465
466 SH_MUTEX_LOCK(mutex_thread_nolog);
467 sh_error_handle(SH_ERR_ALL, FIL__, __LINE__, event->wd, MSG_E_SUBGEN,
468 _("Watch removed: file path unknown"),
469 _("sh_fInotify_process"));
470 SH_MUTEX_UNLOCK(mutex_thread_nolog);
471 }
472 }
473 else if ((event->mask & IN_Q_OVERFLOW) != 0)
474 {
475 SH_INOTIFY_IFUSED( sh.flag.inotify |= SH_INOTIFY_DOSCAN; );
476 SH_INOTIFY_IFUSED( sh.flag.inotify |= SH_INOTIFY_NEEDINIT; );
477
478 SH_MUTEX_LOCK(mutex_thread_nolog);
479 sh_error_handle(SH_ERR_WARN, FIL__, __LINE__, event->wd, MSG_E_SUBGEN,
480 _("Inotify queue overflow"),
481 _("sh_fInotify_process"));
482 SH_MUTEX_UNLOCK(mutex_thread_nolog);
483 return 1;
484 }
485
486 return 0;
487}
488
489void sh_fInotify_report_add(char * path, int class, unsigned long check_flags)
490{
491 if (S_FALSE == sh_ignore_chk_new(path))
492 {
493 int reported = 0;
494
495 sh_files_clear_file_reported(path);
496
497 sh_files_search_file(path, &class, &check_flags, &reported);
498
499 sh_files_filecheck (class, check_flags, path, NULL,
500 &reported, 0);
501 if (SH_FFLAG_REPORTED_SET(reported))
502 sh_files_set_file_reported(path);
503 }
504 return;
505}
506
507
508static void sh_fInotify_report_miss(char * name, int level)
509{
510 char * tmp = sh_util_safe_name (name);
511
512 SH_MUTEX_LOCK(mutex_thread_nolog);
513 sh_error_handle (level, FIL__, __LINE__, 0, MSG_FI_MISS, tmp);
514 SH_MUTEX_UNLOCK(mutex_thread_nolog);
515 ++sh.statistics.files_report;
516 SH_FREE(tmp);
517 return;
518}
519
520static int sh_fInotify_report_change (struct inotify_event * event,
521 char * path, char * filename,
522 int class, unsigned long check_flags, int ftype)
523{
524 int reported;
525 int ret;
526
527
528 if (S_FALSE == sh_ignore_chk_mod(path))
529 {
530 ret = sh_files_search_file(path, &class, &check_flags, &reported);
531
532 if ((ret == 0) && (event->len > 0) && (ftype == SH_INOTIFY_FILE))
533 {
534 ; /* do nothing, watch was for directory monitored as file only */
535 }
536 else
537 {
538 sh_files_filecheck (class, check_flags, filename,
539 (event->len > 0) ? event->name : NULL,
540 &reported, 0);
541 }
542 }
543 return 0;
544}
545
546
547static int sh_fInotify_report_missing (struct inotify_event * event,
548 char * path,
549 int class, unsigned long check_flags, int ftype)
550{
551 int reported;
552 int isdir = (event->mask & IN_ISDIR);
553 int level = (class == SH_LEVEL_ALLIGNORE) ?
554 ShDFLevel[class] :
555 ShDFLevel[(isdir == 0) ? SH_ERR_T_FILE : SH_ERR_T_DIR];
556
557 if (S_FALSE == sh_ignore_chk_del(path))
558 {
559 if (0 != hashreport_missing(path, level))
560 {
561 int ret = sh_files_search_file(path, &class, &check_flags, &reported);
562
563 if ((ret == 0) && (event->len > 0) && (ftype == SH_INOTIFY_FILE))
564 {
565 ; /* do nothing, watch was for directory monitored as file only */
566 }
567 else
568 {
569 /* Removal of a directory triggers:
570 * (1) IN_DELETE IN_ISDIR
571 * (2) IN_DELETE_SELF
572 */
573 if ((event->mask & IN_DELETE_SELF) == 0)
574 sh_fInotify_report_miss(path, level);
575 }
576 }
577 }
578
579 sh_hash_set_missing(path);
580
581 if (sh.flag.reportonce == S_TRUE)
582 sh_files_set_file_reported(path);
583
584 /* Move to 'dormant' list, if not file within directory.
585 */
586 if (event->len == 0)
587 sh_inotify_rm_watch(&sh_file_watches, &sh_file_missing, event->wd);
588
589 return 0;
590}
591
592static int sh_fInotify_report_added (struct inotify_event * event,
593 char * path, char * filename,
594 int class, unsigned long check_flags,
595 int ftype, int rdepth)
596{
597 if (S_FALSE == sh_ignore_chk_new(path))
598 {
599 int reported;
600 int ret;
601 int retD = 0;
602 int rdepthD = rdepth;
603
604 sh_files_clear_file_reported(path);
605
606 ret = sh_files_search_file(path, &class, &check_flags, &reported);
607
608 if ((ret == 0) && (event->len > 0) && (ftype == SH_INOTIFY_FILE))
609 {
610 ; /* do nothing, watch was for directory monitored as file only */
611 }
612 else
613 {
614 int classD = class;
615 int reportedD = reported;
616 unsigned long check_flagsD = check_flags;
617
618 if (event->mask & IN_ISDIR)
619 {
620 retD = sh_files_search_dir(path, &classD, &check_flagsD,
621 &reportedD, &rdepthD);
622 if (retD != 0)
623 {
624 if (ret == 0)
625 {
626 class = classD;
627 check_flags = check_flagsD;
628 }
629 }
630 }
631
632 sh_files_filecheck (class, check_flags, filename,
633 (event->len > 0) ? event->name : NULL,
634 &reported, 0);
635
636 if (event->mask & IN_ISDIR)
637 {
638 SH_INOTIFY_IFUSED( sh.flag.inotify |= SH_INOTIFY_INSCAN; );
639 sh_files_checkdir (classD, check_flagsD, rdepthD,
640 path, (event->len > 0) ? event->name : NULL);
641 SH_INOTIFY_IFUSED( sh.flag.inotify &= ~SH_INOTIFY_INSCAN; );
642 SH_INOTIFY_IFUSED( sh.flag.inotify |= SH_INOTIFY_NEEDINIT; );
643 sh_dirs_reset ();
644 sh_files_reset ();
645 }
646
647 }
648
649 if (SH_FFLAG_REPORTED_SET(reported))
650 sh_files_set_file_reported(path);
651
652 if ((ret != 0) || (event->mask & IN_ISDIR))
653 {
654 sh_inotify_add_watch(path, &sh_file_watches, &ret,
655 class, check_flags,
656 (event->mask & IN_ISDIR)?SH_INOTIFY_DIR:SH_INOTIFY_FILE,
657 rdepthD);
658 }
659 }
660 return 0;
661}
662
663static int sh_fInotify_report(struct inotify_event * event, char * filename,
664 int class, unsigned long check_flags, int ftype, int rdepth)
665{
666 char * fullpath = NULL;
667 char * path;
668
669 if (event->len > 0)
670 {
671 fullpath = sh_util_strconcat(filename, "/", event->name, NULL);
672 path = fullpath;
673 }
674 else
675 {
676 path = filename;
677 }
678
679 if ( (event->mask & (IN_ATTRIB|IN_MODIFY)) != 0)
680 {
681 sh_fInotify_report_change (event, path, filename,
682 class, check_flags, ftype);
683 }
684 else if ((event->mask & (IN_DELETE|IN_DELETE_SELF|IN_MOVE_SELF|IN_MOVED_FROM)) != 0)
685 {
686 sh_fInotify_report_missing (event, path,
687 class, check_flags, ftype);
688 }
689 else if((event->mask & (IN_CREATE|IN_MOVED_TO)) != 0)
690 {
691 sh_fInotify_report_added (event, path, filename,
692 class, check_flags,
693 ftype, rdepth);
694 }
695
696 if (fullpath)
697 SH_FREE(fullpath);
698
699 return 0;
700}
701
702
703#endif
704
705#endif
Note: See TracBrowser for help on using the repository browser.