1 /* SAMHAIN file system integrity testing                                   */
2 /* Copyright (C) 2009 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 #include "config_xor.h"
21 
22 #if defined(HAVE_SYS_INOTIFY_H)
23 
24 #if defined(GCC_VERSION_MAJOR) && !defined(__clang__)
25 #if (GCC_VERSION_MAJOR > 4) || ((GCC_VERSION_MAJOR == 4) && (GCC_VERSION_MINOR > 8))
26 #pragma GCC diagnostic ignored "-Wclobbered"
27 #endif
28 #endif
29 
30 #undef  FIL__
31 #define FIL__  _("sh_inotify.c")
32 
33 /* printf */
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <sys/inotify.h>
38 #include <errno.h>
39 #include <unistd.h>
40 #include <fcntl.h>
41 
42 #include "samhain.h"
43 #include "sh_pthread.h"
44 #include "sh_calls.h"
45 #include "sh_inotify.h"
46 #include "sh_mem.h"
47 #include "sh_utils.h"
48 #include "slib.h"
49 
50 /**************************************************
51  *
52  * Make the inotify fd thread-specific by
53  * encapsulating it in get/set functions:
54  * sh_get_inotify_fd() / sh_set_inotify_fd()
55  *
56  **************************************************/
57 
58 #if defined(HAVE_PTHREAD)
59 
60 SH_MUTEX_STATIC(mutex_list_dormant, PTHREAD_MUTEX_INITIALIZER);
61 SH_MUTEX_STATIC(mutex_watches,      PTHREAD_MUTEX_INITIALIZER);
62 
63 static pthread_key_t  inotify_key;
64 static pthread_once_t inotify_key_once = PTHREAD_ONCE_INIT;
65 
make_inotify_key()66 static void make_inotify_key()
67 {
68     (void) pthread_key_create(&inotify_key, free);
69 }
70 
sh_get_inotify_fd()71 static int sh_get_inotify_fd()
72 {
73   void * ptr;
74   int  * fp;
75 
76   (void) pthread_once(&inotify_key_once, make_inotify_key);
77 
78   if ((ptr = pthread_getspecific(inotify_key)) == NULL)
79     {
80       ptr = calloc(1,sizeof(int));
81       if (ptr)
82 	{
83 	  fp  = (int*) ptr;
84 	  *fp = -1;
85 	  (void) pthread_setspecific(inotify_key, ptr);
86 	}
87       else
88 	{
89 	  return -1;
90 	}
91     }
92   else
93     {
94       fp  = (int*) ptr;
95     }
96   return *fp;
97 }
98 
sh_set_inotify_fd(int fd)99 static void sh_set_inotify_fd(int fd)
100 {
101   int  * fp;
102 
103   fp = (int*) pthread_getspecific(inotify_key);
104   if (fp)
105     *fp = fd;
106   return;
107 }
108 
109 /* !defined(HAVE_PTHREAD) */
110 #else
111 
112 static int sh_inotify_fd = -1;
113 
sh_get_inotify_fd()114 static inline int sh_get_inotify_fd()
115 {
116   return sh_inotify_fd;
117 }
118 
sh_set_inotify_fd(int fd)119 static inline void sh_set_inotify_fd(int fd)
120 {
121   sh_inotify_fd = fd;
122 }
123 
124 #endif
125 
126 /*--- nothing thread-related below this point --- */
127 
128 #include "zAVLTree.h"
129 
130 typedef struct
131 {
132   int    watch;
133   short  flag;
134   short  type;
135   int    class;
136   int    rdepth;
137   unsigned long check_flags;
138   char * file;
139 } sh_watch;
140 
141 /**************************************************
142  *
143  * Get inotify fd, initialize inotify if necessary
144  *
145  **************************************************/
146 #define SH_INOTIFY_FAILED -2
147 
sh_inotify_getfd()148 static int sh_inotify_getfd()
149 {
150   int ifd = sh_get_inotify_fd();
151 
152   if (ifd >= 0)
153     {
154       return ifd;
155     }
156 
157   else if (ifd == SH_INOTIFY_FAILED)
158     {
159       return -1;
160     }
161 
162   else /* if (ifd == -1) */
163     {
164 #if defined(HAVE_INOTIFY_INIT1)
165       ifd = inotify_init1(IN_CLOEXEC);
166 #else
167       ifd = inotify_init();
168       if (ifd >= 0)
169 	{
170 	  long sflags;
171 
172 	  sflags = retry_fcntl(FIL__, __LINE__, ifd, F_GETFD, 0);
173 	  retry_fcntl(FIL__, __LINE__, ifd, F_SETFD, sflags|FD_CLOEXEC);
174 	}
175 #endif
176 
177       if (ifd < 0)
178 	{
179 	  sh_set_inotify_fd(SH_INOTIFY_FAILED);
180 	  return -1;
181 	}
182 
183       sh_set_inotify_fd(ifd);
184       return ifd;
185     }
186 }
187 
188 /**************************************************
189  *
190  * Public function:
191  *  int sh_inotify_wait_for_change(char * filename,
192  *                                 int watch,
193  *                                 int * errnum,
194  *                                 int   waitsec);
195  * Returns: watch, if nonnegative
196  *          -1 on error or reopen required
197  *             (check errnum != 0)
198  *
199  * Caller needs to keep track of watch descriptor
200  *
201  **************************************************/
202 
203 #define SH_INOTIFY_REOPEN 0
204 #define SH_INOTIFY_MODIFY 1
205 
sh_inotify_init(sh_watches * watches)206 void sh_inotify_init(sh_watches * watches)
207 {
208   SH_MUTEX_LOCK_UNSAFE(mutex_watches);
209   watches->list_of_watches = NULL;
210   watches->count           = 0;
211   watches->max_count       = 0;
212   SH_MUTEX_UNLOCK_UNSAFE(mutex_watches);
213 
214   SH_MUTEX_LOCK_UNSAFE(mutex_list_dormant);
215   watches->dormant_watches = NULL;
216   SH_MUTEX_UNLOCK_UNSAFE(mutex_list_dormant);
217 
218   return;
219 }
220 
sh_inotify_read(char * buffer,size_t count)221 ssize_t sh_inotify_read(char * buffer, size_t count)
222 {
223   ssize_t len = -1;
224   int     ifd = sh_inotify_getfd();
225 
226   do {
227     len = read (ifd, buffer, count);
228   } while (len < 0 && (errno == EINTR || errno == EAGAIN));
229 
230   return len;
231 }
232 
sh_inotify_read_timeout(char * buffer,size_t count,int timeout)233 ssize_t sh_inotify_read_timeout(char * buffer, size_t count, int timeout)
234 {
235   ssize_t len;
236   int     ifd = sh_inotify_getfd();
237 
238   len = sl_read_timeout_fd (ifd, buffer, count, timeout, S_FALSE);
239 
240   return len;
241 }
242 
243 
sh_inotify_free_watch(void * item)244 static void sh_inotify_free_watch(void * item)
245 {
246   sh_watch * this = (sh_watch *) item;
247 
248   if (this->file)
249     SH_FREE(this->file);
250   SH_FREE(this);
251   return;
252 }
253 
sh_inotify_create_watch(const char * file,int nwatch,int flag)254 static sh_watch * sh_inotify_create_watch(const char * file,
255 					  int nwatch, int flag)
256 {
257   sh_watch * this = SH_ALLOC(sizeof(sh_watch));
258 
259   this->file  = sh_util_strdup_track(file, __FILE__, __LINE__);
260   this->watch = nwatch;
261   this->flag  = flag;
262   return this;
263 }
264 
265 /********** List Handling ******************/
266 
267 struct sh_inotify_litem
268 {
269   sh_watch * watch;
270   struct sh_inotify_litem * next;
271 };
272 
sh_inotify_listitem_destroy(struct sh_inotify_litem * this)273 static void sh_inotify_listitem_destroy(struct sh_inotify_litem * this)
274 {
275   if (this)
276     SH_FREE(this);
277   return;
278 }
279 
280 /* No Mutex in the list cursor functions, must be in the caller
281  * function...
282  */
283 typedef struct {
284   struct sh_inotify_litem *prenode;
285   struct sh_inotify_litem *curnode;
286 } sh_inotify_listCursor;
287 
sh_inotify_list_first(sh_inotify_listCursor * listcursor,sh_watches * watches)288 static sh_watch * sh_inotify_list_first(sh_inotify_listCursor * listcursor,
289 					sh_watches * watches)
290 {
291   listcursor->prenode = watches->dormant_watches;
292   listcursor->curnode = watches->dormant_watches;
293 
294   if (listcursor->curnode)
295     return listcursor->curnode->watch;
296   return NULL;
297 }
298 
sh_inotify_list_next(sh_inotify_listCursor * listcursor,sh_watches * watches)299 static sh_watch * sh_inotify_list_next(sh_inotify_listCursor * listcursor,
300 				       sh_watches * watches)
301 {
302   (void) watches;
303 
304   listcursor->prenode = listcursor->curnode;
305 
306   if (listcursor->curnode)
307     {
308       listcursor->curnode = listcursor->curnode->next;
309       if (listcursor->curnode)
310 	return listcursor->curnode->watch;
311       else
312 	return NULL;
313     }
314 
315   return NULL;
316 }
317 
sh_inotify_list_del_cur(sh_inotify_listCursor * listcursor,sh_watches * watches)318 static sh_watch * sh_inotify_list_del_cur(sh_inotify_listCursor * listcursor,
319 					  sh_watches * watches)
320 {
321   sh_watch * ret = NULL;
322 
323   if (listcursor->curnode)
324     {
325       struct sh_inotify_litem * this = listcursor->curnode;
326 
327       if (listcursor->prenode == this)
328 	{
329 	  watches->dormant_watches = this->next;
330 
331 	  listcursor->prenode = watches->dormant_watches;
332 	  listcursor->curnode = watches->dormant_watches;
333 	}
334       else
335 	{
336 	  listcursor->prenode->next = this->next;
337 	  listcursor->curnode       = this->next;
338 	}
339       if (listcursor->curnode)
340 	ret = listcursor->curnode->watch;
341       else
342 	ret = NULL;
343       sh_inotify_listitem_destroy(this);
344     }
345   return ret;
346 }
347 
sh_inotify_add_dormant(sh_watches * watches,sh_watch * item)348 static int sh_inotify_add_dormant(sh_watches * watches, sh_watch * item)
349 {
350   struct sh_inotify_litem * this;
351 
352   SH_MUTEX_LOCK(mutex_list_dormant);
353   this = SH_ALLOC(sizeof(struct sh_inotify_litem));
354 
355   this->watch = item;
356   this->next  = (struct sh_inotify_litem *) watches->dormant_watches;
357 
358   watches->dormant_watches = this;
359   SH_MUTEX_UNLOCK(mutex_list_dormant);
360   return 0;
361 }
362 
363 static void * sh_dummy_popret = NULL;
364 
sh_inotify_pop_dormant(sh_watches * watches,int * class,unsigned long * check_flags,int * type,int * rdepth)365 char * sh_inotify_pop_dormant(sh_watches * watches,
366 			      int * class, unsigned long * check_flags,
367 			      int * type, int * rdepth)
368 {
369   char * popret = NULL;
370   struct sh_inotify_litem * this;
371 
372   /* Take the address to keep gcc from putting it into a register.
373    * Avoids the 'clobbered by longjmp' warning.
374    */
375   sh_dummy_popret = (void *) &popret;
376 
377   SH_MUTEX_LOCK(mutex_list_dormant);
378 
379   this = (struct sh_inotify_litem *) watches->dormant_watches;
380 
381   if (this)
382     {
383       *class  = this->watch->class;
384       *type   = this->watch->type;
385       *rdepth = this->watch->rdepth;
386       *check_flags = this->watch->check_flags;
387       popret  = sh_util_strdup_track(this->watch->file, __FILE__, __LINE__);
388 
389       watches->dormant_watches = this->next;
390 
391       sh_inotify_free_watch(this->watch);
392       SH_FREE(this);
393     }
394   SH_MUTEX_UNLOCK(mutex_list_dormant);
395 
396   sh_dummy_popret = NULL;
397   return popret;
398 }
399 
sh_inotify_purge_dormant(sh_watches * watches)400 void sh_inotify_purge_dormant(sh_watches * watches)
401 {
402   struct sh_inotify_litem * this;
403 
404   SH_MUTEX_LOCK(mutex_list_dormant);
405   this = (struct sh_inotify_litem *) watches->dormant_watches;
406 
407   watches->dormant_watches = NULL;
408 
409   while (this)
410     {
411       struct sh_inotify_litem * cur = this;
412 
413       this = this->next;
414 
415       sh_inotify_free_watch(cur->watch);
416       SH_FREE(cur);
417     }
418   SH_MUTEX_UNLOCK(mutex_list_dormant);
419   return;
420 }
421 
422 /********** End List Handling **************/
423 
sh_inotify_getkey(void const * item)424 static zAVLKey sh_inotify_getkey(void const *item)
425 {
426   return (&((const sh_watch *)item)->watch);
427 }
428 
sh_inotify_close()429 void sh_inotify_close()
430 {
431   int     ifd = sh_inotify_getfd();
432 
433   if (ifd >= 0)
434     close(ifd);
435   sh_set_inotify_fd(-1);
436 
437   return;
438 }
439 
440 
441 /* This function removes all watches from the list,
442  * and closes the inode file descriptor in this thread.
443  */
sh_inotify_remove(sh_watches * watches)444 void sh_inotify_remove(sh_watches * watches)
445 {
446   zAVLTree   * all_watches;
447 
448   SH_MUTEX_LOCK(mutex_watches);
449   all_watches = (zAVLTree *)(watches->list_of_watches);
450 
451   if (all_watches)
452     zAVLFreeTree(all_watches, sh_inotify_free_watch);
453 
454   watches->list_of_watches = NULL;
455   watches->count = 0;
456   SH_MUTEX_UNLOCK(mutex_watches);
457 
458   sh_inotify_close();
459   return;
460 }
461 
index_watched_file(char * filename,sh_watches * watches)462 static int index_watched_file(char * filename, sh_watches * watches)
463 {
464   sh_watch   * item;
465   zAVLCursor   avlcursor;
466   zAVLTree   * all_watches = (zAVLTree *)(watches->list_of_watches);
467 
468   if (all_watches)
469     {
470       for (item = (sh_watch *) zAVLFirst(&avlcursor, all_watches); item;
471 	   item = (sh_watch *) zAVLNext(&avlcursor))
472 	{
473 	  if (item->file)
474 	    {
475 	      if (0 == strcmp(filename, item->file))
476 		return item->watch;
477 	    }
478 	}
479     }
480   return -1;
481 }
482 
483 #if !defined(IN_DONT_FOLLOW)
484 #define IN_DONT_FOLLOW 0
485 #endif
486 
487 #define SH_INOTIFY_FILEFLAGS \
488   (IN_ATTRIB|IN_MODIFY|IN_DELETE_SELF|IN_MOVE_SELF|IN_UNMOUNT|IN_DONT_FOLLOW)
489 #define SH_INOTIFY_DIRFLAGS \
490   (SH_INOTIFY_FILEFLAGS|IN_DELETE|IN_CREATE|IN_MOVED_FROM|IN_MOVED_TO)
491 
492 #define SH_INOTIFY_FLAGS (SH_INOTIFY_FILEFLAGS|SH_INOTIFY_DIRFLAGS)
493 
494 
495 /* Create an item and put it on the 'dormant' list for later watch creation
496  */
sh_inotify_add_watch_later(const char * filename,sh_watches * watches,int * errnum,int class,unsigned long check_flags,int type,int rdepth)497 int sh_inotify_add_watch_later(const char * filename, sh_watches * watches,
498 			       int * errnum,
499 			       int class, unsigned long check_flags, int type,
500 			       int rdepth)
501 {
502   sh_watch   * item;
503 
504   item = sh_inotify_create_watch(filename, -1, /* flag */ 0);
505 
506   item->class      = class;
507   item->type       = (short) type;
508   item->rdepth     = (short) rdepth;
509   item->check_flags = check_flags;
510 
511   sh_inotify_add_dormant(watches, item);
512   if (errnum)
513     *errnum = 0;
514 
515   return 0;
516 }
517 
sh_inotify_rm_watch(sh_watches * watches,sh_watches * save,int wd)518 int sh_inotify_rm_watch (sh_watches * watches, sh_watches * save, int wd)
519 {
520   int ifd = sh_get_inotify_fd();
521 
522   if (watches)
523     {
524       sh_watch   * item;
525 
526       SH_MUTEX_LOCK(mutex_watches);
527       item = zAVLSearch(watches->list_of_watches, &wd);
528 
529       if (item)
530 	{
531 	  zAVLDelete(watches->list_of_watches, &wd);
532 	  if (save) /* optionally save the item */
533 	    {
534 	      item->watch = -1;
535 	      sh_inotify_add_dormant(save, item);
536 	    }
537 	  else
538 	    {
539 	      sh_inotify_free_watch(item);
540 	    }
541 	}
542       SH_MUTEX_UNLOCK(mutex_watches);
543     }
544   return inotify_rm_watch(ifd, wd);
545 }
546 
547 #if (defined(SH_WITH_CLIENT) || defined(SH_STANDALONE))
548 static void * sh_dummy_litem;
549 
sh_inotify_recheck_watches(sh_watches * watches,sh_watches * save)550 int sh_inotify_recheck_watches (sh_watches * watches, sh_watches * save)
551 {
552   sh_watch   * litem;
553   sh_inotify_listCursor listcursor;
554   int ifd = sh_get_inotify_fd();
555 
556   extern void sh_fInotify_report_add(char * path,
557 				     int class, unsigned long check_flags);
558 
559   sh_dummy_litem = (void*) &litem;
560 
561   /* -- Check dormant watches for reopening.
562    */
563   SH_MUTEX_LOCK(mutex_list_dormant);
564 
565   litem = sh_inotify_list_first(&listcursor, save);
566 
567   while (litem)
568     {
569     have_next:
570 
571       /* sh_inotify_list_del_cur may return NULL */
572       if (litem && litem->file && litem->watch == -1)
573 	{
574 	  litem->watch = inotify_add_watch (ifd, litem->file,
575 					    SH_INOTIFY_FLAGS);
576 
577 	  if (litem->watch >= 0)
578 	    {
579 	      SH_MUTEX_LOCK(mutex_watches);
580 	      if (watches->list_of_watches)
581 		zAVLInsert(watches->list_of_watches, litem);
582 	      SH_MUTEX_UNLOCK(mutex_watches);
583 
584 	      sh_fInotify_report_add(litem->file, litem->class, litem->check_flags);
585 
586 	      litem = sh_inotify_list_del_cur(&listcursor, save);
587 
588 	      goto have_next;
589 	    }
590 	}
591       litem = sh_inotify_list_next(&listcursor, save);
592     }
593   SH_MUTEX_UNLOCK(mutex_list_dormant);
594   return 0;
595 }
596 #endif
597 
598 /* This function is idempotent; it will add the watch only once
599  */
sh_inotify_add_watch(char * filename,sh_watches * watches,int * errnum,int class,unsigned long check_flags,int type,int rdepth)600 int sh_inotify_add_watch(char * filename, sh_watches * watches, int * errnum,
601 			 int class, unsigned long check_flags, int type, int rdepth)
602 {
603   volatile int retval = 0;
604 
605   SH_MUTEX_LOCK(mutex_watches);
606 
607   *errnum = 0;
608 
609   if (filename)
610     {
611       int nwatch;
612       sh_watch   * item;
613       int index = index_watched_file(filename, watches);
614 
615       if (index < 0)
616 	{
617 	  int     ifd = sh_inotify_getfd();
618 
619 	  /*************************************
620 
621 	  if (watches->count == SH_INOTIFY_MAX)
622 	    {
623 #ifdef EMFILE
624 	      *errnum = EMFILE;
625 #else
626 	      *errnum = 24;
627 #endif
628 	      return -1;
629 	    }
630 	  **************************************/
631 
632 	  nwatch = inotify_add_watch (ifd, filename,
633 				      SH_INOTIFY_FLAGS);
634 	  if (nwatch < 0)
635 	    {
636 	      *errnum = errno;
637 	      retval = -1;
638 	      goto retpoint;
639 	    }
640 
641 	  item = sh_inotify_create_watch(filename, nwatch, /* flag */ 0);
642 
643 	  item->class      = class;
644 	  item->type       = type;
645 	  item->rdepth     = rdepth;
646 	  item->check_flags = check_flags;
647 
648 	  if (NULL == watches->list_of_watches)
649 	    watches->list_of_watches = zAVLAllocTree (sh_inotify_getkey,
650 						      zAVL_KEY_INT);
651 
652 	  if (watches->list_of_watches)
653 	    {
654 	      *errnum =  zAVLInsert((zAVLTree *)(watches->list_of_watches),
655 				    item);
656 
657 	      if (*errnum != 0)
658 		{
659 		  /* zAVLInsert returns -1 on malloc() error and 3 if
660 		   * the node already exists.
661 		   */
662 		  *errnum = (*errnum == -1) ? ENOMEM : EEXIST;
663 		  sh_inotify_free_watch(item);
664 		  retval = -1;
665 		  goto retpoint;
666 		}
667 	    }
668 	  else
669 	    {
670 	      *errnum = ENOMEM;
671 	      sh_inotify_free_watch(item);
672 	      retval = -1;
673 	      goto retpoint;
674 	    }
675 
676 	  ++(watches->count);
677 	}
678       else if (type == SH_INOTIFY_DIR) /* watch exists */
679 	{
680 	  /* This covers the case that a directory has been added,
681 	   * but is watched as file at first because it is also
682 	   * specified as file in the config.
683 	   */
684 	  item = zAVLSearch(watches->list_of_watches, &index);
685 
686 	  if (item && item->type == SH_INOTIFY_FILE)
687 	    {
688 	      item->type = SH_INOTIFY_DIR;
689 	    }
690 	}
691     }
692  retpoint:
693   ; /* 'label at end of compound statement' */
694   SH_MUTEX_UNLOCK(mutex_watches);
695   return retval;
696 }
697 
698 static void * sh_dummy_sret = NULL;
699 
sh_inotify_search_item(sh_watches * watches,int watch,int * class,unsigned long * check_flags,int * type,int * rdepth)700 char * sh_inotify_search_item(sh_watches * watches, int watch,
701 			      int * class, unsigned long * check_flags,
702 			      int * type, int * rdepth)
703 {
704   sh_watch * item;
705   char     * sret = NULL;
706 
707   /* Take the address to keep gcc from putting it into a register.
708    * Avoids the 'clobbered by longjmp' warning.
709    */
710   sh_dummy_sret = (void *) &sret;
711 
712   SH_MUTEX_LOCK(mutex_watches);
713   item = zAVLSearch(watches->list_of_watches, &watch);
714 
715   if (item)
716     {
717       *class      = item->class;
718       *check_flags = item->check_flags;
719       *type       = item->type;
720       *rdepth     = item->rdepth;
721       sret = sh_util_strdup_track(item->file, __FILE__, __LINE__);
722     }
723   SH_MUTEX_UNLOCK(mutex_watches);
724   return sret;
725 }
726 
727 static void * sh_dummy_litem = NULL;
728 
sh_inotify_wait_for_change(char * filename,sh_watches * watches,int * errnum,int waitsec)729 int sh_inotify_wait_for_change(char * filename, sh_watches * watches,
730 			       int  * errnum, int waitsec)
731 {
732   sh_watch   * litem;
733   sh_watch   * zitem;
734   int          ifd = sh_inotify_getfd();
735 
736   /* Take the address to keep gcc from putting it into a register.
737    * Avoids the 'clobbered by longjmp' warning.
738    */
739   sh_dummy_litem = (void*) &litem;
740 
741   *errnum = 0;
742 
743  start_it:
744 
745   if (ifd >= 0)
746     {
747       volatile ssize_t  i  = 0;
748       ssize_t len = -1;
749       int  flag = 0;
750       char buffer[1024];
751 
752       sh_inotify_listCursor listcursor;
753 
754       /* -- Add watch if required
755        */
756       if (filename)
757 	{
758 	  if (sh_inotify_add_watch(filename, watches, errnum,
759 				   0, 0, SH_INOTIFY_FILE, 0) < 0)
760 	    {
761 	      retry_msleep(waitsec, 0);
762 	      return -1;
763 	    }
764 	}
765 
766       /* -- Check dormant watches for reopening.
767        */
768       SH_MUTEX_LOCK(mutex_list_dormant);
769 
770       for (litem = sh_inotify_list_first(&listcursor, watches); litem;
771 	   litem = sh_inotify_list_next(&listcursor, watches))
772 	{
773 	have_next:
774 	  /* sh_inotify_list_del_cur may return NULL */
775 	  if (litem && litem->file && litem->watch == -1)
776 	    {
777 	      litem->watch = inotify_add_watch (ifd, litem->file,
778 						SH_INOTIFY_FLAGS);
779 
780 	      if (litem->watch >= 0)
781 		{
782 		  SH_MUTEX_LOCK(mutex_watches);
783 		  if (watches->list_of_watches)
784 		    zAVLInsert(watches->list_of_watches, litem);
785 		  SH_MUTEX_UNLOCK(mutex_watches);
786 		  litem = sh_inotify_list_del_cur(&listcursor, watches);
787 		  goto have_next;
788 		}
789 	    }
790 	}
791       SH_MUTEX_UNLOCK(mutex_list_dormant);
792 
793 
794       /* -- Blocking read on inotify file descriptor
795        */
796       len = sh_inotify_read(buffer, sizeof(buffer));
797 
798       if (len > 0)
799 	{
800 	  struct inotify_event *event;
801 
802 	  i = 0;
803 
804 	  while (i < len) {
805 
806 	    event = (struct inotify_event *) &buffer[i];
807 
808 	    SH_MUTEX_LOCK(mutex_watches);
809 	    zitem = zAVLSearch(watches->list_of_watches, &(event->wd));
810 
811 	    if (zitem)
812 	      {
813 		if (event->mask & IN_MODIFY)
814 		  {
815 		    zitem->flag |= SH_INOTIFY_MODIFY;
816 		    flag |= SH_INOTIFY_MODIFY;
817 		  }
818 		else if (event->mask & IN_DELETE_SELF ||
819 			 event->mask & IN_UNMOUNT     ||
820 			 event->mask & IN_MOVE_SELF   )
821 		  {
822 		    zitem->flag |= SH_INOTIFY_REOPEN;
823 		    (void) inotify_rm_watch(ifd, zitem->watch);
824 		    zAVLDelete(watches->list_of_watches, zitem);
825 		    sh_inotify_add_dormant(watches, zitem);
826 		    zitem->watch    = -1;
827 		    flag |= SH_INOTIFY_REOPEN;
828 		  }
829 	      }
830 	    SH_MUTEX_UNLOCK(mutex_watches);
831 
832 	    i += sizeof (struct inotify_event) + event->len;
833 	  }
834 	}
835       else if (len == -1)
836 	{
837 	  *errnum = errno;
838 	  retry_msleep(waitsec, 0);
839 
840 	  return -1;
841 	}
842 
843       if (flag & SH_INOTIFY_REOPEN)
844 	{
845 	  if (flag & SH_INOTIFY_MODIFY)
846 	    return 0;
847 	  else
848 	    goto start_it;
849 	}
850 
851       return 0;
852     }
853 
854   /* Inotify not working, sleep
855    */
856   retry_msleep(waitsec, 0);
857 
858   *errnum = 0;
859   return -1;
860 }
861 
862 
863 /* !defined(HAVE_SYS_INOTIFY_H) */
864 #else
865 
866 #include "sh_calls.h"
867 #include "sh_inotify.h"
868 
sh_inotify_remove(sh_watches * watches)869 void sh_inotify_remove(sh_watches * watches)
870 {
871   (void) watches;
872   return;
873 }
874 
sh_inotify_wait_for_change(char * filename,sh_watches * watches,int * errnum,int waitsec)875 int sh_inotify_wait_for_change(char * filename, sh_watches * watches,
876 			       int *  errnum, int waitsec)
877 {
878   (void) filename;
879   (void) watches;
880 
881   /* Inotify not working, sleep for waitsec seconds
882    */
883   retry_msleep(waitsec, 0);
884 
885   if (errnum)
886     *errnum = 0;
887   return -1;
888 }
889 
sh_inotify_add_watch(char * filename,sh_watches * watches,int * errnum,int class,unsigned long check_flags,int type,int rdepth)890 int sh_inotify_add_watch(char * filename, sh_watches * watches, int  * errnum,
891 			 int class, unsigned long check_flags, int type, int rdepth)
892 {
893   (void) filename;
894   (void) watches;
895   (void) class;
896   (void) check_flags;
897   (void) type;
898   (void) rdepth;
899 
900   if (errnum)
901     *errnum = 0;
902   return 0;
903 }
904 
sh_inotify_add_watch_later(const char * filename,sh_watches * watches,int * errnum,int class,unsigned long check_flags,int type,int rdepth)905 int sh_inotify_add_watch_later(const char * filename, sh_watches * watches,
906 			       int  * errnum,
907 			       int class, unsigned long check_flags, int type, int rdepth)
908 {
909   (void) filename;
910   (void) watches;
911   (void) class;
912   (void) check_flags;
913   (void) type;
914   (void) rdepth;
915 
916   if (errnum)
917     *errnum = 0;
918   return 0;
919 }
920 
921 #endif
922 
923 #ifdef SH_CUTEST
924 #include "CuTest.h"
Test_inotify(CuTest * tc)925 void Test_inotify(CuTest *tc) {
926 #if defined(HAVE_SYS_INOTIFY_H) && (defined(SH_WITH_CLIENT) || defined(SH_STANDALONE))
927 
928   int          ret;
929   sh_watches   twatch = SH_INOTIFY_INITIALIZER;
930   sh_watch   * litem;
931   sh_inotify_listCursor listcursor;
932   char * p;
933   int class;
934   int type;
935   int rdepth;
936   unsigned long check_flags;
937   int           nrun = 0;
938 
939   sh_watch aw1 = { -1, 0, 0, 1, 99, 1, "a1" };
940   sh_watch aw2 = { -1, 0, 0, 2, 99, 1, "a2" };
941   sh_watch aw3 = {  2, 0, 0, 3, 99, 1, "a3" };
942   sh_watch aw4 = { -1, 0, 0, 4, 99, 1, "a4" };
943   sh_watch aw5 = {  5, 0, 0, 5, 99, 1, "a5" };
944 
945   do {
946 
947     int          count = 0;
948 
949     sh_watch * w1 = SH_ALLOC(sizeof(sh_watch));
950     sh_watch * w2 = SH_ALLOC(sizeof(sh_watch));
951     sh_watch * w3 = SH_ALLOC(sizeof(sh_watch));
952     sh_watch * w4 = SH_ALLOC(sizeof(sh_watch));
953     sh_watch * w5 = SH_ALLOC(sizeof(sh_watch));
954 
955     memcpy(w1, &aw1, sizeof(sh_watch));
956     w1->file = sh_util_strdup(aw1.file);
957     memcpy(w2, &aw2, sizeof(sh_watch));
958     w2->file = sh_util_strdup(aw2.file);
959     memcpy(w3, &aw3, sizeof(sh_watch));
960     w3->file = sh_util_strdup(aw3.file);
961     memcpy(w4, &aw4, sizeof(sh_watch));
962     w4->file = sh_util_strdup(aw4.file);
963     memcpy(w5, &aw5, sizeof(sh_watch));
964     w5->file = sh_util_strdup(aw5.file);
965 
966     ret = sh_inotify_add_dormant(&twatch, w1);
967     CuAssertIntEquals(tc, ret, 0);
968     ret = sh_inotify_add_dormant(&twatch, w2);
969     CuAssertIntEquals(tc, ret, 0);
970     ret = sh_inotify_add_dormant(&twatch, w3);
971     CuAssertIntEquals(tc, ret, 0);
972     ret = sh_inotify_add_dormant(&twatch, w4);
973     CuAssertIntEquals(tc, ret, 0);
974     ret = sh_inotify_add_dormant(&twatch, w5);
975     CuAssertIntEquals(tc, ret, 0);
976 
977     /* -- Check dormant watches for reopening.
978      */
979     for (litem = sh_inotify_list_first(&listcursor, &twatch); litem;
980 	 litem = sh_inotify_list_next(&listcursor, &twatch))
981       {
982       have_next:
983 
984 	/* sh_inotify_list_del_cur may return NULL */
985 	if (litem)
986 	  {
987 	    ++count;
988 
989 	    if (litem->file && litem->watch == -1)
990 	      {
991 
992 		switch (litem->class)
993 		  {
994 		  case 1:
995 		    CuAssertStrEquals(tc, litem->file, "a1");
996 		    break;
997 		  case 2:
998 		    CuAssertStrEquals(tc, litem->file, "a2");
999 		    break;
1000 		  case 3:
1001 		    CuAssertStrEquals(tc, litem->file, "deadbeef");
1002 		    break;
1003 		  case 4:
1004 		    CuAssertStrEquals(tc, litem->file, "a4");
1005 		    break;
1006 		  case 5:
1007 		    CuAssertStrEquals(tc, litem->file, "deadbeef");
1008 		    break;
1009 		  default:
1010 		    CuAssertStrEquals(tc, litem->file, "deadbeef");
1011 		  }
1012 		litem = sh_inotify_list_del_cur(&listcursor, &twatch);
1013 		goto have_next;
1014 	      }
1015 	    switch (litem->class)
1016 	      {
1017 	      case 3:
1018 		CuAssertStrEquals(tc, litem->file, "a3");
1019 		break;
1020 	      case 5:
1021 		CuAssertStrEquals(tc, litem->file, "a5");
1022 		break;
1023 	      default:
1024 		CuAssertStrEquals(tc, litem->file, "foobar");
1025 	      }
1026 	  }
1027       }
1028 
1029     CuAssertIntEquals(tc, count, 5);
1030 
1031     p = sh_inotify_pop_dormant(&twatch, &class, &check_flags, &type, &rdepth);
1032     CuAssertStrEquals(tc, p, "a5");
1033 
1034     p = sh_inotify_pop_dormant(&twatch, &class, &check_flags, &type, &rdepth);
1035     CuAssertStrEquals(tc, p, "a3");
1036     CuAssertIntEquals(tc, class, 3);
1037 
1038     p = sh_inotify_pop_dormant(&twatch, &class, &check_flags, &type, &rdepth);
1039     CuAssertTrue(tc, NULL == p);
1040     CuAssertTrue(tc, NULL == twatch.dormant_watches);
1041 
1042     ++nrun;
1043 
1044   } while (nrun < 100);
1045 
1046 #else
1047   (void) tc;
1048 #endif
1049 
1050   return;
1051 }
1052 #endif
1053