Coverage Report

Created: 2025-12-31 06:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/samba/source3/smbd/notify_inotify.c
Line
Count
Source
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.h"
28
#include "smbd/globals.h"
29
30
#include <sys/inotify.h>
31
32
/* glibc < 2.5 headers don't have these defines */
33
#ifndef IN_ONLYDIR
34
#define IN_ONLYDIR 0x01000000
35
#endif
36
#ifndef IN_MASK_ADD
37
#define IN_MASK_ADD 0x20000000
38
#endif
39
40
struct inotify_private {
41
  struct sys_notify_context *ctx;
42
  int fd;
43
  struct inotify_watch_context *watches;
44
};
45
46
struct inotify_watch_context {
47
  struct inotify_watch_context *next, *prev;
48
  struct inotify_private *in;
49
  int wd;
50
  void (*callback)(struct sys_notify_context *ctx,
51
       void *private_data,
52
       struct notify_event *ev,
53
       uint32_t filter);
54
  void *private_data;
55
  uint32_t mask; /* the inotify mask */
56
  uint32_t filter; /* the windows completion filter */
57
  const char *path;
58
59
  char *last_mkdir;
60
  struct inotify_event *moved_from_event;
61
};
62
63
64
/*
65
  map from a change notify mask to a inotify mask. Remove any bits
66
  which we can handle
67
*/
68
static const struct {
69
  uint32_t notify_mask;
70
  uint32_t inotify_mask;
71
} inotify_mapping[] = {
72
  {FILE_NOTIFY_CHANGE_FILE_NAME,   IN_CREATE|IN_DELETE|IN_MOVED_FROM|IN_MOVED_TO},
73
  {FILE_NOTIFY_CHANGE_DIR_NAME,    IN_CREATE|IN_DELETE|IN_MOVED_FROM|IN_MOVED_TO},
74
  {FILE_NOTIFY_CHANGE_ATTRIBUTES,  IN_ATTRIB|IN_MOVED_TO|IN_MOVED_FROM|IN_MODIFY},
75
  {FILE_NOTIFY_CHANGE_LAST_WRITE,  IN_ATTRIB},
76
  {FILE_NOTIFY_CHANGE_LAST_ACCESS, IN_ATTRIB},
77
  {FILE_NOTIFY_CHANGE_EA,          IN_ATTRIB},
78
  {FILE_NOTIFY_CHANGE_SECURITY,    IN_ATTRIB}
79
};
80
81
static uint32_t inotify_map(uint32_t *filter)
82
0
{
83
0
  size_t i;
84
0
  uint32_t out=0;
85
0
  for (i=0;i<ARRAY_SIZE(inotify_mapping);i++) {
86
0
    if (inotify_mapping[i].notify_mask & *filter) {
87
0
      out |= inotify_mapping[i].inotify_mask;
88
0
      *filter &= ~inotify_mapping[i].notify_mask;
89
0
    }
90
0
  }
91
0
  return out;
92
0
}
93
94
/*
95
 * Map inotify mask back to filter. This returns all filters that
96
 * could have created the inotify watch.
97
 */
98
static uint32_t inotify_map_mask_to_filter(uint32_t mask)
99
0
{
100
0
  size_t i;
101
0
  uint32_t filter = 0;
102
103
0
  for (i = 0; i < ARRAY_SIZE(inotify_mapping); i++) {
104
0
    if (inotify_mapping[i].inotify_mask & mask) {
105
0
      filter |= inotify_mapping[i].notify_mask;
106
0
    }
107
0
  }
108
109
0
  if (mask & IN_ISDIR) {
110
0
    filter &= ~FILE_NOTIFY_CHANGE_FILE_NAME;
111
0
  } else {
112
0
    filter &= ~FILE_NOTIFY_CHANGE_DIR_NAME;
113
0
  }
114
115
0
  return filter;
116
0
}
117
118
/*
119
  destroy the inotify private context
120
*/
121
static int inotify_destructor(struct inotify_private *in)
122
0
{
123
0
  close(in->fd);
124
0
  return 0;
125
0
}
126
127
128
/*
129
  see if a particular event from inotify really does match a requested
130
  notify event in SMB
131
*/
132
static bool filter_match(struct inotify_watch_context *w,
133
       struct inotify_event *e)
134
0
{
135
0
  bool ok;
136
137
0
  DEBUG(10, ("filter_match: e->mask=%x, w->mask=%x, w->filter=%x\n",
138
0
       e->mask, w->mask, w->filter));
139
140
0
  if ((e->mask & w->mask) == 0) {
141
    /* this happens because inotify_add_watch() coalesces watches on the same
142
       path, oring their masks together */
143
0
    return False;
144
0
  }
145
146
  /* SMB separates the filters for files and directories */
147
0
  if (e->mask & IN_ISDIR) {
148
0
    ok = ((w->filter & FILE_NOTIFY_CHANGE_DIR_NAME) != 0);
149
0
    return ok;
150
0
  }
151
152
0
  if ((e->mask & IN_ATTRIB) &&
153
0
      (w->filter & (FILE_NOTIFY_CHANGE_ATTRIBUTES|
154
0
        FILE_NOTIFY_CHANGE_LAST_WRITE|
155
0
        FILE_NOTIFY_CHANGE_LAST_ACCESS|
156
0
        FILE_NOTIFY_CHANGE_EA|
157
0
        FILE_NOTIFY_CHANGE_SECURITY))) {
158
0
    return True;
159
0
  }
160
0
  if ((e->mask & IN_MODIFY) &&
161
0
      (w->filter & FILE_NOTIFY_CHANGE_ATTRIBUTES)) {
162
0
    return True;
163
0
  }
164
165
0
  ok = ((w->filter & FILE_NOTIFY_CHANGE_FILE_NAME) != 0);
166
0
  return ok;
167
0
}
168
169
static void trigger_orphaned_moved_from(struct inotify_watch_context *w)
170
0
{
171
0
  struct notify_event ne = {};
172
0
  struct inotify_event *e = w->moved_from_event;
173
174
0
  ne = (struct notify_event){
175
0
    .action = NOTIFY_ACTION_REMOVED,
176
0
    .path = e->name,
177
0
    .dir = w->path,
178
0
  };
179
180
0
  w->callback(w->in->ctx,
181
0
        w->private_data,
182
0
        &ne,
183
0
        inotify_map_mask_to_filter(e->mask));
184
0
}
185
186
static void moved_from_timeout(struct tevent_context *ev,
187
             struct tevent_timer *te,
188
             struct timeval now,
189
             void *private_data)
190
0
{
191
0
  struct inotify_watch_context *w = talloc_get_type_abort(
192
0
    private_data, struct inotify_watch_context);
193
194
0
  trigger_orphaned_moved_from(w);
195
0
  TALLOC_FREE(w->moved_from_event);
196
0
}
197
198
static void save_moved_from(struct tevent_context *ev,
199
          struct inotify_watch_context *w,
200
          struct inotify_event *e)
201
0
{
202
0
  if (w->moved_from_event != NULL) {
203
0
    trigger_orphaned_moved_from(w);
204
0
    TALLOC_FREE(w->moved_from_event);
205
0
  }
206
207
0
  w->moved_from_event = talloc_memdup(
208
0
    w, e, sizeof(struct inotify_event) + e->len);
209
0
  if (w->moved_from_event == NULL) {
210
    /*
211
     * Not much we can do here
212
     */
213
0
    return;
214
0
  }
215
216
0
  tevent_add_timer(ev,
217
0
       w->moved_from_event,
218
0
       tevent_timeval_current_ofs(0, 100000),
219
0
       moved_from_timeout,
220
0
       w);
221
0
}
222
223
static bool handle_local_rename(struct inotify_watch_context *w,
224
        struct inotify_event *to)
225
0
{
226
0
  struct inotify_private *in = w->in;
227
0
  struct inotify_event *from = w->moved_from_event;
228
0
  struct notify_event ne = {};
229
0
  uint32_t filter;
230
231
0
  if ((w->last_mkdir != NULL) && (w->moved_from_event != NULL) &&
232
0
      IS_SMBD_TMPNAME(w->moved_from_event->name, NULL))
233
0
  {
234
0
    if (strcmp(to->name, w->last_mkdir) == 0) {
235
      /*
236
       * Assume this is a rename() from smbd after a
237
       * mkdir of the real target directory. See the
238
       * comment about RENAME_NOREPLACE in
239
       * mkdir_internals(). We have already sent out
240
       * the mkdir notify event, this MOVED_FROM/TO
241
       * pair is just internal fluff that the client
242
       * should not get wind of via notify.
243
       */
244
0
      TALLOC_FREE(w->last_mkdir);
245
0
      TALLOC_FREE(w->moved_from_event);
246
0
      return true;
247
0
    }
248
249
0
    if (strcmp(w->moved_from_event->name, w->last_mkdir) == 0) {
250
      /*
251
       * Assume this is a renameat2() from smbd's
252
       * mkdir_internal().
253
       */
254
0
      TALLOC_FREE(w->last_mkdir);
255
0
      TALLOC_FREE(w->moved_from_event);
256
257
      /*
258
       * Pretend this is not a rename but a new
259
       * directory.
260
       */
261
0
      to->mask = IN_ISDIR | IN_CREATE;
262
263
0
      return false;
264
0
    }
265
0
  }
266
267
0
  ne = (struct notify_event){
268
0
    .action = NOTIFY_ACTION_OLD_NAME,
269
0
    .path = from->name,
270
0
    .dir = w->path,
271
0
  };
272
273
0
  if (filter_match(w, from)) {
274
0
    filter = inotify_map_mask_to_filter(to->mask);
275
0
    w->callback(in->ctx, w->private_data, &ne, filter);
276
0
  }
277
278
0
  ne = (struct notify_event){
279
0
    .action = NOTIFY_ACTION_NEW_NAME,
280
0
    .path = to->name,
281
0
    .dir = w->path,
282
0
  };
283
284
0
  filter = inotify_map_mask_to_filter(to->mask);
285
286
0
  if (filter_match(w, to)) {
287
0
    w->callback(in->ctx, w->private_data, &ne, filter);
288
0
  }
289
290
0
  if (to->mask & IN_ISDIR) {
291
0
    return true;
292
0
  }
293
0
  if ((w->filter & FILE_NOTIFY_CHANGE_CREATION) == 0) {
294
0
    return true;
295
0
  }
296
297
  /*
298
   * SMB expects a file rename to generate three events, two for
299
   * the rename and the other for a modify of the
300
   * destination. Strange!
301
   */
302
303
0
  ne.action = NOTIFY_ACTION_MODIFIED;
304
0
  to->mask = IN_ATTRIB;
305
306
0
  if (filter_match(w, to)) {
307
0
    w->callback(in->ctx, w->private_data, &ne, filter);
308
0
  }
309
310
0
  return true;
311
0
}
312
313
/*
314
  dispatch one inotify event
315
316
  the cookies are used to correctly handle renames
317
*/
318
static void inotify_dispatch(struct tevent_context *ev,
319
           struct inotify_private *in,
320
           struct inotify_event *e)
321
0
{
322
0
  struct inotify_watch_context *w, *next;
323
0
  struct notify_event ne;
324
0
  uint32_t filter;
325
326
0
  DBG_DEBUG("called with mask=%x, name=[%s]\n",
327
0
      e->mask, e->len ? e->name : "");
328
329
  /* ignore extraneous events, such as unmount and IN_IGNORED events */
330
0
  if ((e->mask & (IN_ATTRIB|IN_MODIFY|IN_CREATE|IN_DELETE|
331
0
      IN_MOVED_FROM|IN_MOVED_TO)) == 0) {
332
0
    return;
333
0
  }
334
335
0
  if (e->mask & IN_MOVED_FROM) {
336
0
    for (w = in->watches; w != NULL; w = w->next) {
337
0
      if (e->wd != w->wd) {
338
0
        continue;
339
0
      }
340
0
      save_moved_from(ev, w, e);
341
0
    }
342
0
    return;
343
0
  }
344
345
0
  if (e->mask & IN_MOVED_TO) {
346
0
    for (w = in->watches; w != NULL; w = w->next) {
347
0
      if ((w->wd == e->wd) &&
348
0
          (w->moved_from_event != NULL) &&
349
0
          (w->moved_from_event->cookie == e->cookie))
350
0
      {
351
0
        bool handled = handle_local_rename(w, e);
352
0
        if (handled) {
353
0
          return;
354
0
        }
355
0
      }
356
0
    }
357
0
  }
358
359
0
  if ((e->mask & IN_CREATE) && (e->mask & IN_ISDIR)) {
360
0
    for (w = in->watches; w != NULL; w = w->next) {
361
0
      if (w->wd != e->wd) {
362
0
        continue;
363
0
      }
364
0
      TALLOC_FREE(w->last_mkdir);
365
0
      w->last_mkdir = talloc_strdup(w, e->name);
366
0
    }
367
368
0
    if (IS_SMBD_TMPNAME(e->name, NULL)) {
369
0
      return;
370
0
    }
371
0
  }
372
373
0
  if (e->mask & IN_CREATE) {
374
0
    ne.action = NOTIFY_ACTION_ADDED;
375
0
  } else if (e->mask & IN_DELETE) {
376
0
    ne.action = NOTIFY_ACTION_REMOVED;
377
0
  } else if (e->mask & IN_MOVED_TO) {
378
0
    ne.action = NOTIFY_ACTION_ADDED;
379
0
  } else {
380
0
    ne.action = NOTIFY_ACTION_MODIFIED;
381
0
  }
382
0
  ne.path = e->name;
383
384
0
  filter = inotify_map_mask_to_filter(e->mask);
385
386
0
  DBG_DEBUG("ne.action = %d, ne.path = %s, filter = %d\n",
387
0
      ne.action, ne.path, filter);
388
389
  /* find any watches that have this watch descriptor */
390
0
  for (w=in->watches;w;w=next) {
391
0
    next = w->next;
392
0
    if (w->wd == e->wd && filter_match(w, e)) {
393
0
      ne.dir = w->path;
394
0
      w->callback(in->ctx, w->private_data, &ne, filter);
395
0
    }
396
0
  }
397
0
}
398
399
/*
400
  called when the kernel has some events for us
401
*/
402
static void inotify_handler(struct tevent_context *ev, struct tevent_fd *fde,
403
          uint16_t flags, void *private_data)
404
0
{
405
0
  struct inotify_private *in = talloc_get_type(private_data,
406
0
                 struct inotify_private);
407
0
  char buf[sizeof(struct inotify_event) + NAME_MAX + 1];
408
0
  int bufsize = 0;
409
0
  struct inotify_event *e = NULL;
410
0
  ssize_t ret;
411
412
0
  ret = sys_read(in->fd, buf, sizeof(buf));
413
0
  if (ret == -1) {
414
0
    DEBUG(0, ("Failed to read all inotify data - %s\n",
415
0
        strerror(errno)));
416
    /* the inotify fd will now be out of sync,
417
     * can't keep reading data off it */
418
0
    TALLOC_FREE(fde);
419
0
    return;
420
0
  }
421
0
  bufsize = ret;
422
423
0
  e = (struct inotify_event *)buf;
424
425
  /* we can get more than one event in the buffer */
426
0
  while (bufsize >= sizeof(struct inotify_event)) {
427
0
    size_t e_len = sizeof(struct inotify_event) + e->len;
428
429
0
    if ((e_len < sizeof(struct inotify_event)) ||
430
0
        (e_len > bufsize))
431
0
    {
432
0
      DBG_ERR("Invalid data from inotify\n");
433
0
      TALLOC_FREE(fde);
434
0
      return;
435
0
    }
436
437
0
    inotify_dispatch(ev, in, e);
438
439
0
    e = (struct inotify_event *)((char *)e + e_len);
440
0
    bufsize -= e_len;
441
0
  }
442
0
}
443
444
/*
445
  setup the inotify handle - called the first time a watch is added on
446
  this context
447
*/
448
static int inotify_setup(struct sys_notify_context *ctx)
449
0
{
450
0
  struct inotify_private *in;
451
0
  struct tevent_fd *fde;
452
453
0
  in = talloc(ctx, struct inotify_private);
454
0
  if (in == NULL) {
455
0
    return ENOMEM;
456
0
  }
457
458
0
  in->fd = inotify_init();
459
0
  if (in->fd == -1) {
460
0
    int ret = errno;
461
0
    DEBUG(0, ("Failed to init inotify - %s\n", strerror(ret)));
462
0
    talloc_free(in);
463
0
    return ret;
464
0
  }
465
0
  in->ctx = ctx;
466
0
  in->watches = NULL;
467
468
0
  ctx->private_data = in;
469
0
  talloc_set_destructor(in, inotify_destructor);
470
471
  /* add a event waiting for the inotify fd to be readable */
472
0
  fde = tevent_add_fd(ctx->ev, in, in->fd, TEVENT_FD_READ,
473
0
          inotify_handler, in);
474
0
  if (fde == NULL) {
475
0
    ctx->private_data = NULL;
476
0
    TALLOC_FREE(in);
477
0
    return ENOMEM;
478
0
  }
479
0
  return 0;
480
0
}
481
482
/*
483
  destroy a watch
484
*/
485
static int watch_destructor(struct inotify_watch_context *w)
486
0
{
487
0
  struct inotify_private *in = w->in;
488
0
  int wd = w->wd;
489
0
  DLIST_REMOVE(w->in->watches, w);
490
491
0
  for (w=in->watches;w;w=w->next) {
492
0
    if (w->wd == wd) {
493
      /*
494
       * Another inotify_watch_context listens on this path,
495
       * leave the kernel level watch in place
496
       */
497
0
      return 0;
498
0
    }
499
0
  }
500
501
0
  DEBUG(10, ("Deleting inotify watch %d\n", wd));
502
0
  if (inotify_rm_watch(in->fd, wd) == -1) {
503
0
    DEBUG(1, ("inotify_rm_watch returned %s\n", strerror(errno)));
504
0
  }
505
0
  return 0;
506
0
}
507
508
509
/*
510
  add a watch. The watch is removed when the caller calls
511
  talloc_free() on *handle
512
*/
513
int inotify_watch(TALLOC_CTX *mem_ctx,
514
      struct sys_notify_context *ctx,
515
      const char *path,
516
      uint32_t *filter,
517
      uint32_t *subdir_filter,
518
      void (*callback)(struct sys_notify_context *ctx,
519
           void *private_data,
520
           struct notify_event *ev,
521
           uint32_t filter),
522
      void *private_data,
523
      void *handle_p)
524
0
{
525
0
  struct inotify_private *in;
526
0
  uint32_t mask;
527
0
  struct inotify_watch_context *w;
528
0
  uint32_t orig_filter = *filter;
529
0
  void **handle = (void **)handle_p;
530
531
  /* maybe setup the inotify fd */
532
0
  if (ctx->private_data == NULL) {
533
0
    int ret;
534
0
    ret = inotify_setup(ctx);
535
0
    if (ret != 0) {
536
0
      return ret;
537
0
    }
538
0
  }
539
540
0
  in = talloc_get_type(ctx->private_data, struct inotify_private);
541
542
0
  mask = inotify_map(filter);
543
0
  if (mask == 0) {
544
    /* this filter can't be handled by inotify */
545
0
    return EINVAL;
546
0
  }
547
548
  /* using IN_MASK_ADD allows us to cope with inotify() returning the same
549
     watch descriptor for multiple watches on the same path */
550
0
  mask |= (IN_MASK_ADD | IN_ONLYDIR);
551
552
0
  w = talloc(mem_ctx, struct inotify_watch_context);
553
0
  if (w == NULL) {
554
0
    *filter = orig_filter;
555
0
    return ENOMEM;
556
0
  }
557
558
0
  *w = (struct inotify_watch_context) {
559
0
    .in = in,
560
0
    .callback = callback,
561
0
    .private_data = private_data,
562
0
    .mask = mask,
563
0
    .filter = orig_filter,
564
0
    .path = talloc_strdup(w, path),
565
0
  };
566
567
0
  if (w->path == NULL) {
568
0
    *filter = orig_filter;
569
0
    TALLOC_FREE(w);
570
0
    return ENOMEM;
571
0
  }
572
573
  /* get a new watch descriptor for this path */
574
0
  w->wd = inotify_add_watch(in->fd, path, mask);
575
0
  if (w->wd == -1) {
576
0
    int err = errno;
577
0
    *filter = orig_filter;
578
0
    TALLOC_FREE(w);
579
0
    DEBUG(1, ("inotify_add_watch returned %s\n", strerror(err)));
580
0
    return err;
581
0
  }
582
583
0
  DEBUG(10, ("inotify_add_watch for %s mask %x returned wd %d\n",
584
0
       path, mask, w->wd));
585
586
0
  (*handle) = w;
587
588
0
  DLIST_ADD(in->watches, w);
589
590
  /* the caller frees the handle to stop watching */
591
0
  talloc_set_destructor(w, watch_destructor);
592
593
0
  return 0;
594
0
}