LCOV - code coverage report
Current view: top level - source3/smbd - notify_inotify.c (source / functions) Hit Total Coverage
Test: coverage report for master 2f515e9b Lines: 0 182 0.0 %
Date: 2024-04-21 15:09:00 Functions: 0 9 0.0 %

          Line data    Source code
       1             : /* 
       2             :    Unix SMB/CIFS implementation.
       3             : 
       4             :    Copyright (C) Andrew Tridgell 2006
       5             : 
       6             :    This program is free software; you can redistribute it and/or modify
       7             :    it under the terms of the GNU General Public License as published by
       8             :    the Free Software Foundation; either version 3 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, see <http://www.gnu.org/licenses/>.
      18             : */
      19             : 
      20             : /*
      21             :   notify implementation using inotify
      22             : */
      23             : 
      24             : #include "includes.h"
      25             : #include "../librpc/gen_ndr/notify.h"
      26             : #include "smbd/smbd.h"
      27             : #include "lib/util/sys_rw_data.h"
      28             : 
      29             : #include <sys/inotify.h>
      30             : 
      31             : /* glibc < 2.5 headers don't have these defines */
      32             : #ifndef IN_ONLYDIR
      33             : #define IN_ONLYDIR 0x01000000
      34             : #endif
      35             : #ifndef IN_MASK_ADD
      36             : #define IN_MASK_ADD 0x20000000
      37             : #endif
      38             : 
      39             : struct inotify_private {
      40             :         struct sys_notify_context *ctx;
      41             :         int fd;
      42             :         struct inotify_watch_context *watches;
      43             : };
      44             : 
      45             : struct inotify_watch_context {
      46             :         struct inotify_watch_context *next, *prev;
      47             :         struct inotify_private *in;
      48             :         int wd;
      49             :         void (*callback)(struct sys_notify_context *ctx, 
      50             :                          void *private_data,
      51             :                          struct notify_event *ev,
      52             :                          uint32_t filter);
      53             :         void *private_data;
      54             :         uint32_t mask; /* the inotify mask */
      55             :         uint32_t filter; /* the windows completion filter */
      56             :         const char *path;
      57             : };
      58             : 
      59             : 
      60             : /*
      61             :   map from a change notify mask to a inotify mask. Remove any bits
      62             :   which we can handle
      63             : */
      64             : static const struct {
      65             :         uint32_t notify_mask;
      66             :         uint32_t inotify_mask;
      67             : } inotify_mapping[] = {
      68             :         {FILE_NOTIFY_CHANGE_FILE_NAME,   IN_CREATE|IN_DELETE|IN_MOVED_FROM|IN_MOVED_TO},
      69             :         {FILE_NOTIFY_CHANGE_DIR_NAME,    IN_CREATE|IN_DELETE|IN_MOVED_FROM|IN_MOVED_TO},
      70             :         {FILE_NOTIFY_CHANGE_ATTRIBUTES,  IN_ATTRIB|IN_MOVED_TO|IN_MOVED_FROM|IN_MODIFY},
      71             :         {FILE_NOTIFY_CHANGE_LAST_WRITE,  IN_ATTRIB},
      72             :         {FILE_NOTIFY_CHANGE_LAST_ACCESS, IN_ATTRIB},
      73             :         {FILE_NOTIFY_CHANGE_EA,          IN_ATTRIB},
      74             :         {FILE_NOTIFY_CHANGE_SECURITY,    IN_ATTRIB}
      75             : };
      76             : 
      77           0 : static uint32_t inotify_map(uint32_t *filter)
      78             : {
      79           0 :         size_t i;
      80           0 :         uint32_t out=0;
      81           0 :         for (i=0;i<ARRAY_SIZE(inotify_mapping);i++) {
      82           0 :                 if (inotify_mapping[i].notify_mask & *filter) {
      83           0 :                         out |= inotify_mapping[i].inotify_mask;
      84           0 :                         *filter &= ~inotify_mapping[i].notify_mask;
      85             :                 }
      86             :         }
      87           0 :         return out;
      88             : }
      89             : 
      90             : /*
      91             :  * Map inotify mask back to filter. This returns all filters that
      92             :  * could have created the inotify watch.
      93             :  */
      94           0 : static uint32_t inotify_map_mask_to_filter(uint32_t mask)
      95             : {
      96           0 :         size_t i;
      97           0 :         uint32_t filter = 0;
      98             : 
      99           0 :         for (i = 0; i < ARRAY_SIZE(inotify_mapping); i++) {
     100           0 :                 if (inotify_mapping[i].inotify_mask & mask) {
     101           0 :                         filter |= inotify_mapping[i].notify_mask;
     102             :                 }
     103             :         }
     104             : 
     105           0 :         if (mask & IN_ISDIR) {
     106           0 :                 filter &= ~FILE_NOTIFY_CHANGE_FILE_NAME;
     107             :         } else {
     108           0 :                 filter &= ~FILE_NOTIFY_CHANGE_DIR_NAME;
     109             :         }
     110             : 
     111           0 :         return filter;
     112             : }
     113             : 
     114             : /*
     115             :   destroy the inotify private context
     116             : */
     117           0 : static int inotify_destructor(struct inotify_private *in)
     118             : {
     119           0 :         close(in->fd);
     120           0 :         return 0;
     121             : }
     122             : 
     123             : 
     124             : /*
     125             :   see if a particular event from inotify really does match a requested
     126             :   notify event in SMB
     127             : */
     128           0 : static bool filter_match(struct inotify_watch_context *w,
     129             :                          struct inotify_event *e)
     130             : {
     131           0 :         bool ok;
     132             : 
     133           0 :         DEBUG(10, ("filter_match: e->mask=%x, w->mask=%x, w->filter=%x\n",
     134             :                    e->mask, w->mask, w->filter));
     135             : 
     136           0 :         if ((e->mask & w->mask) == 0) {
     137             :                 /* this happens because inotify_add_watch() coalesces watches on the same
     138             :                    path, oring their masks together */
     139           0 :                 return False;
     140             :         }
     141             : 
     142             :         /* SMB separates the filters for files and directories */
     143           0 :         if (e->mask & IN_ISDIR) {
     144           0 :                 ok = ((w->filter & FILE_NOTIFY_CHANGE_DIR_NAME) != 0);
     145           0 :                 return ok;
     146             :         }
     147             : 
     148           0 :         if ((e->mask & IN_ATTRIB) &&
     149           0 :             (w->filter & (FILE_NOTIFY_CHANGE_ATTRIBUTES|
     150             :                           FILE_NOTIFY_CHANGE_LAST_WRITE|
     151             :                           FILE_NOTIFY_CHANGE_LAST_ACCESS|
     152             :                           FILE_NOTIFY_CHANGE_EA|
     153             :                           FILE_NOTIFY_CHANGE_SECURITY))) {
     154           0 :                 return True;
     155             :         }
     156           0 :         if ((e->mask & IN_MODIFY) &&
     157           0 :             (w->filter & FILE_NOTIFY_CHANGE_ATTRIBUTES)) {
     158           0 :                 return True;
     159             :         }
     160             : 
     161           0 :         ok = ((w->filter & FILE_NOTIFY_CHANGE_FILE_NAME) != 0);
     162           0 :         return ok;
     163             : }
     164             : 
     165             : 
     166             : 
     167             : /*
     168             :   dispatch one inotify event
     169             : 
     170             :   the cookies are used to correctly handle renames
     171             : */
     172           0 : static void inotify_dispatch(struct inotify_private *in, 
     173             :                              struct inotify_event *e, 
     174             :                              int prev_wd,
     175             :                              uint32_t prev_cookie,
     176             :                              struct inotify_event *e2)
     177             : {
     178           0 :         struct inotify_watch_context *w, *next;
     179           0 :         struct notify_event ne;
     180           0 :         uint32_t filter;
     181             : 
     182           0 :         DEBUG(10, ("inotify_dispatch called with mask=%x, name=[%s]\n",
     183             :                    e->mask, e->len ? e->name : ""));
     184             : 
     185             :         /* ignore extraneous events, such as unmount and IN_IGNORED events */
     186           0 :         if ((e->mask & (IN_ATTRIB|IN_MODIFY|IN_CREATE|IN_DELETE|
     187             :                         IN_MOVED_FROM|IN_MOVED_TO)) == 0) {
     188           0 :                 return;
     189             :         }
     190             : 
     191             :         /* map the inotify mask to a action. This gets complicated for
     192             :            renames */
     193           0 :         if (e->mask & IN_CREATE) {
     194           0 :                 ne.action = NOTIFY_ACTION_ADDED;
     195           0 :         } else if (e->mask & IN_DELETE) {
     196           0 :                 ne.action = NOTIFY_ACTION_REMOVED;
     197           0 :         } else if (e->mask & IN_MOVED_FROM) {
     198           0 :                 if (e2 != NULL && e2->cookie == e->cookie &&
     199           0 :                     e2->wd == e->wd) {
     200           0 :                         ne.action = NOTIFY_ACTION_OLD_NAME;
     201             :                 } else {
     202           0 :                         ne.action = NOTIFY_ACTION_REMOVED;
     203             :                 }
     204           0 :         } else if (e->mask & IN_MOVED_TO) {
     205           0 :                 if ((e->cookie == prev_cookie) && (e->wd == prev_wd)) {
     206           0 :                         ne.action = NOTIFY_ACTION_NEW_NAME;
     207             :                 } else {
     208           0 :                         ne.action = NOTIFY_ACTION_ADDED;
     209             :                 }
     210             :         } else {
     211           0 :                 ne.action = NOTIFY_ACTION_MODIFIED;
     212             :         }
     213           0 :         ne.path = e->name;
     214             : 
     215           0 :         filter = inotify_map_mask_to_filter(e->mask);
     216             : 
     217           0 :         DBG_DEBUG("ne.action = %d, ne.path = %s, filter = %d\n",
     218             :                   ne.action, ne.path, filter);
     219             : 
     220             :         /* find any watches that have this watch descriptor */
     221           0 :         for (w=in->watches;w;w=next) {
     222           0 :                 next = w->next;
     223           0 :                 if (w->wd == e->wd && filter_match(w, e)) {
     224           0 :                         ne.dir = w->path;
     225           0 :                         w->callback(in->ctx, w->private_data, &ne, filter);
     226             :                 }
     227             :         }
     228             : 
     229           0 :         if ((ne.action == NOTIFY_ACTION_NEW_NAME) &&
     230           0 :             ((e->mask & IN_ISDIR) == 0)) {
     231             : 
     232             :                 /*
     233             :                  * SMB expects a file rename to generate three events, two for
     234             :                  * the rename and the other for a modify of the
     235             :                  * destination. Strange!
     236             :                  */
     237             : 
     238           0 :                 ne.action = NOTIFY_ACTION_MODIFIED;
     239           0 :                 e->mask = IN_ATTRIB;
     240             : 
     241           0 :                 for (w=in->watches;w;w=next) {
     242           0 :                         next = w->next;
     243           0 :                         if (w->wd == e->wd && filter_match(w, e) &&
     244           0 :                             !(w->filter & FILE_NOTIFY_CHANGE_CREATION)) {
     245           0 :                                 ne.dir = w->path;
     246           0 :                                 w->callback(in->ctx, w->private_data, &ne,
     247             :                                             filter);
     248             :                         }
     249             :                 }
     250             :         }
     251             : }
     252             : 
     253             : /*
     254             :   called when the kernel has some events for us
     255             : */
     256           0 : static void inotify_handler(struct tevent_context *ev, struct tevent_fd *fde,
     257             :                             uint16_t flags, void *private_data)
     258             : {
     259           0 :         struct inotify_private *in = talloc_get_type(private_data,
     260             :                                                      struct inotify_private);
     261           0 :         int bufsize = 0;
     262           0 :         struct inotify_event *e0, *e;
     263           0 :         uint32_t prev_cookie=0;
     264           0 :         int prev_wd = -1;
     265           0 :         ssize_t ret;
     266             : 
     267             :         /*
     268             :           we must use FIONREAD as we cannot predict the length of the
     269             :           filenames, and thus can't know how much to allocate
     270             :           otherwise
     271             :         */
     272           0 :         if (ioctl(in->fd, FIONREAD, &bufsize) != 0 || 
     273           0 :             bufsize == 0) {
     274           0 :                 DEBUG(0,("No data on inotify fd?!\n"));
     275           0 :                 TALLOC_FREE(fde);
     276           0 :                 return;
     277             :         }
     278             : 
     279           0 :         e0 = e = (struct inotify_event *)TALLOC_SIZE(in, bufsize + 1);
     280           0 :         if (e == NULL) return;
     281           0 :         ((uint8_t *)e)[bufsize] = '\0';
     282             : 
     283           0 :         ret = read_data(in->fd, e0, bufsize);
     284           0 :         if (ret != bufsize) {
     285           0 :                 DEBUG(0, ("Failed to read all inotify data - %s\n",
     286             :                           strerror(errno)));
     287           0 :                 talloc_free(e0);
     288             :                 /* the inotify fd will now be out of sync,
     289             :                  * can't keep reading data off it */
     290           0 :                 TALLOC_FREE(fde);
     291           0 :                 return;
     292             :         }
     293             : 
     294             :         /* we can get more than one event in the buffer */
     295           0 :         while (e && (bufsize >= sizeof(*e))) {
     296           0 :                 struct inotify_event *e2 = NULL;
     297           0 :                 bufsize -= e->len + sizeof(*e);
     298           0 :                 if (bufsize >= sizeof(*e)) {
     299           0 :                         e2 = (struct inotify_event *)(e->len + sizeof(*e) + (char *)e);
     300             :                 }
     301           0 :                 inotify_dispatch(in, e, prev_wd, prev_cookie, e2);
     302           0 :                 prev_wd = e->wd;
     303           0 :                 prev_cookie = e->cookie;
     304           0 :                 e = e2;
     305             :         }
     306             : 
     307           0 :         talloc_free(e0);
     308             : }
     309             : 
     310             : /*
     311             :   setup the inotify handle - called the first time a watch is added on
     312             :   this context
     313             : */
     314           0 : static int inotify_setup(struct sys_notify_context *ctx)
     315             : {
     316           0 :         struct inotify_private *in;
     317           0 :         struct tevent_fd *fde;
     318             : 
     319           0 :         in = talloc(ctx, struct inotify_private);
     320           0 :         if (in == NULL) {
     321           0 :                 return ENOMEM;
     322             :         }
     323             : 
     324           0 :         in->fd = inotify_init();
     325           0 :         if (in->fd == -1) {
     326           0 :                 int ret = errno;
     327           0 :                 DEBUG(0, ("Failed to init inotify - %s\n", strerror(ret)));
     328           0 :                 talloc_free(in);
     329           0 :                 return ret;
     330             :         }
     331           0 :         in->ctx = ctx;
     332           0 :         in->watches = NULL;
     333             : 
     334           0 :         ctx->private_data = in;
     335           0 :         talloc_set_destructor(in, inotify_destructor);
     336             : 
     337             :         /* add a event waiting for the inotify fd to be readable */
     338           0 :         fde = tevent_add_fd(ctx->ev, in, in->fd, TEVENT_FD_READ,
     339             :                             inotify_handler, in);
     340           0 :         if (fde == NULL) {
     341           0 :                 ctx->private_data = NULL;
     342           0 :                 TALLOC_FREE(in);
     343           0 :                 return ENOMEM;
     344             :         }
     345           0 :         return 0;
     346             : }
     347             : 
     348             : /*
     349             :   destroy a watch
     350             : */
     351           0 : static int watch_destructor(struct inotify_watch_context *w)
     352             : {
     353           0 :         struct inotify_private *in = w->in;
     354           0 :         int wd = w->wd;
     355           0 :         DLIST_REMOVE(w->in->watches, w);
     356             : 
     357           0 :         for (w=in->watches;w;w=w->next) {
     358           0 :                 if (w->wd == wd) {
     359             :                         /*
     360             :                          * Another inotify_watch_context listens on this path,
     361             :                          * leave the kernel level watch in place
     362             :                          */
     363           0 :                         return 0;
     364             :                 }
     365             :         }
     366             : 
     367           0 :         DEBUG(10, ("Deleting inotify watch %d\n", wd));
     368           0 :         if (inotify_rm_watch(in->fd, wd) == -1) {
     369           0 :                 DEBUG(1, ("inotify_rm_watch returned %s\n", strerror(errno)));
     370             :         }
     371           0 :         return 0;
     372             : }
     373             : 
     374             : 
     375             : /*
     376             :   add a watch. The watch is removed when the caller calls
     377             :   talloc_free() on *handle
     378             : */
     379           0 : int inotify_watch(TALLOC_CTX *mem_ctx,
     380             :                   struct sys_notify_context *ctx,
     381             :                   const char *path,
     382             :                   uint32_t *filter,
     383             :                   uint32_t *subdir_filter,
     384             :                   void (*callback)(struct sys_notify_context *ctx,
     385             :                                    void *private_data,
     386             :                                    struct notify_event *ev,
     387             :                                    uint32_t filter),
     388             :                   void *private_data,
     389             :                   void *handle_p)
     390             : {
     391           0 :         struct inotify_private *in;
     392           0 :         uint32_t mask;
     393           0 :         struct inotify_watch_context *w;
     394           0 :         uint32_t orig_filter = *filter;
     395           0 :         void **handle = (void **)handle_p;
     396             : 
     397             :         /* maybe setup the inotify fd */
     398           0 :         if (ctx->private_data == NULL) {
     399           0 :                 int ret;
     400           0 :                 ret = inotify_setup(ctx);
     401           0 :                 if (ret != 0) {
     402           0 :                         return ret;
     403             :                 }
     404             :         }
     405             : 
     406           0 :         in = talloc_get_type(ctx->private_data, struct inotify_private);
     407             : 
     408           0 :         mask = inotify_map(filter);
     409           0 :         if (mask == 0) {
     410             :                 /* this filter can't be handled by inotify */
     411           0 :                 return EINVAL;
     412             :         }
     413             : 
     414             :         /* using IN_MASK_ADD allows us to cope with inotify() returning the same
     415             :            watch descriptor for multiple watches on the same path */
     416           0 :         mask |= (IN_MASK_ADD | IN_ONLYDIR);
     417             : 
     418           0 :         w = talloc(mem_ctx, struct inotify_watch_context);
     419           0 :         if (w == NULL) {
     420           0 :                 *filter = orig_filter;
     421           0 :                 return ENOMEM;
     422             :         }
     423             : 
     424           0 :         w->in = in;
     425           0 :         w->callback = callback;
     426           0 :         w->private_data = private_data;
     427           0 :         w->mask = mask;
     428           0 :         w->filter = orig_filter;
     429           0 :         w->path = talloc_strdup(w, path);
     430           0 :         if (w->path == NULL) {
     431           0 :                 *filter = orig_filter;
     432           0 :                 TALLOC_FREE(w);
     433           0 :                 return ENOMEM;
     434             :         }
     435             : 
     436             :         /* get a new watch descriptor for this path */
     437           0 :         w->wd = inotify_add_watch(in->fd, path, mask);
     438           0 :         if (w->wd == -1) {
     439           0 :                 int err = errno;
     440           0 :                 *filter = orig_filter;
     441           0 :                 TALLOC_FREE(w);
     442           0 :                 DEBUG(1, ("inotify_add_watch returned %s\n", strerror(err)));
     443           0 :                 return err;
     444             :         }
     445             : 
     446           0 :         DEBUG(10, ("inotify_add_watch for %s mask %x returned wd %d\n",
     447             :                    path, mask, w->wd));
     448             : 
     449           0 :         (*handle) = w;
     450             : 
     451           0 :         DLIST_ADD(in->watches, w);
     452             : 
     453             :         /* the caller frees the handle to stop watching */
     454           0 :         talloc_set_destructor(w, watch_destructor);
     455             : 
     456           0 :         return 0;
     457             : }

Generated by: LCOV version 1.14