Skip to content

Latest commit

 

History

History
560 lines (476 loc) · 12 KB

filewatcher.c

File metadata and controls

560 lines (476 loc) · 12 KB
 
May 17, 2019
May 17, 2019
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* @file filewatcher.c
* Mode Control Entity - flag file tracking
* <p>
* Copyright (C) 2013-2019 Jolla Ltd.
* <p>
* @author Simo Piiroinen <simo.piiroinen@jollamobile.com>
*
* mce is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License
* version 2.1 as published by the Free Software Foundation.
*
* mce is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with mce. If not, see <http://www.gnu.org/licenses/>.
*/
21
22
#include "filewatcher.h"
May 27, 2014
May 27, 2014
23
24
25
26
27
28
#include "mce-log.h"
#include <sys/inotify.h>
#include <unistd.h>
May 27, 2014
May 27, 2014
29
#include <string.h>
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
#include <errno.h>
#define DEBUG_INOTIFY_EVENTS 0
static inline void *lea(const void *base, int offs)
{
return ((char *)base)+offs;
}
/* ------------------------------------------------------------------------- *
* Inotify event debugging helpers
* ------------------------------------------------------------------------- */
#if DEBUG_INOTIFY_EVENTS
/** Convert inotify event bitmask to human readable string
*
* @param mask event bit mask
* @param buff where to construct the string
* @param size how much space buff has (must be > 0)
*
* @return string with names of set bits separated with '+'
*/
static
const char *
inotify_mask_repr(uint32_t mask, char *buff, size_t size)
{
static const struct
{
uint32_t mask;
const char *name;
} lut[] =
{
# define X(tag) { .mask = IN_##tag, .name = #tag },
X(ACCESS)
X(MODIFY)
X(ATTRIB)
X(CLOSE_WRITE)
X(CLOSE_NOWRITE)
X(OPEN)
X(MOVED_FROM)
X(MOVED_TO)
X(CREATE)
X(DELETE)
X(DELETE_SELF)
X(MOVE_SELF)
X(UNMOUNT)
X(Q_OVERFLOW)
X(IGNORED)
X(ONLYDIR)
X(DONT_FOLLOW)
X(EXCL_UNLINK)
X(MASK_ADD)
X(ISDIR)
X(ONESHOT)
# undef X
{ .mask = 0, .name = 0 }
};
char *pos = buff;
char *end = buff + size - 1;
auto void adds(const char *s)
{
while( *s && pos < end) *pos++ = *s++;
}
for( size_t i = 0; lut[i].mask; ++i )
{
if( mask & lut[i].mask )
{
mask ^= lut[i].mask;
if( pos > buff ) adds("+");
adds(lut[i].name);
}
}
if( mask )
{
char hex[32];
snprintf(hex, sizeof hex, "0x%"PRIx32, mask);
if( pos > buff ) adds("+");
adds(hex);
}
return *pos = 0, buff;
}
/** Emit inotify event details
*
* @param eve inotify_event pointer
*/
static
void
inotify_event_debug(const struct inotify_event *eve)
{
char temp[256];
printf("wd=%d\n", eve->wd);
printf("mask=%s\n", inotify_mask_repr(eve->mask, temp, sizeof temp));
if( eve->len )
{
printf("name=\"%s\"\n", eve->name);
}
printf("\n");
}
#endif /* DEBUG_INOTIFY_EVENTS */
/* ------------------------------------------------------------------------- *
* File content change tracking
* ------------------------------------------------------------------------- */
/** Object for tracking file content in a directory */
struct filewatcher_t
{
/** inotify file descriptor */
int inotify_fd;
/** inotify watch descriptor */
int inotify_wd;
/** glib input watch for inotify_fd */
guint watch_id;
/** the directory to watch over */
char *watch_path;
/** the file in the watch_path to track */
char *watch_file;
/** function to call when watch_path/watch_file changes */
filewatcher_changed_fn changed_cb;
/** user data to pass to changed_cb */
gpointer user_data;
/** how to delete user_data when filewatcher_t is deleted */
GDestroyNotify delete_cb;
};
/* Initialize filewatcher_t object to a sane state
*
* @param self pointer to uninitialized filewatcher_t object
*/
static
void
filewatcher_ctor(filewatcher_t *self)
{
self->inotify_fd = -1;
self->inotify_wd = -1;
self->watch_path = 0;
self->watch_file = 0;
self->watch_id = 0;
self->changed_cb = 0;
self->delete_cb = 0;
self->user_data = 0;
}
/* Release all dynamic data from filewatcher_t object
*
* @param self pointer to initialized filewatcher_t object
*/
static
void
filewatcher_dtor(filewatcher_t *self)
{
/* detach user data */
if( self->delete_cb )
{
self->delete_cb(self->user_data);
}
self->user_data = 0;
/* detach glib io watch */
if( self->watch_id )
{
g_source_remove(self->watch_id), self->watch_id = 0;
}
/* detach inotify fd */
if( self->inotify_fd != -1 )
{
if( self->inotify_wd != -1 )
{
if( inotify_rm_watch(self->inotify_fd, self->inotify_wd) == -1 )
{
mce_log(LL_WARN, "inotify_rm_watch: %m");
}
self->inotify_wd = -1;
}
if( close(self->inotify_fd) == -1 )
{
mce_log(LL_WARN, "close inotify fd: %m");
}
self->inotify_fd = -1;
}
/* release strings */
g_free(self->watch_path), self->watch_path = 0;
g_free(self->watch_file), self->watch_file = 0;
}
/* Delete a filewatcher_t object
*
* @param self pointer to initialized filewatcher_t object, or NULL
*/
void
filewatcher_delete(filewatcher_t *self)
{
if( self != 0 )
{
filewatcher_dtor(self);
g_free(self);
}
}
/** Process inotify events
*
* @param self pointer to filewatcher_t object
*
* @return TRUE on success, or FALSE if further processing is not possible
*/
static
gboolean
filewatcher_process_events(filewatcher_t *self)
{
gboolean res = FALSE;
gboolean flg = FALSE;
char buf[2048];
int todo, size;
struct inotify_event *eve;
if( !self || self->inotify_fd == -1 )
{
goto cleanup;
}
todo = read(self->inotify_fd, buf, sizeof buf);
if( todo < 0 )
{
switch( errno )
{
case EAGAIN:
case EINTR:
res = TRUE;
break;
default:
mce_log(LL_WARN, "read inotify events: %m");
break;
}
goto cleanup;
}
if( todo == 0 )
{
mce_log(LL_WARN, "read inotify events: EOF");
goto cleanup;
}
#if DEBUG_INOTIFY_EVENTS
printf("----\n");
#endif
for( eve = lea(buf, 0); todo; todo -= size, eve = lea(eve, size))
{
if( todo < (int)sizeof *eve )
{
mce_log(LL_WARN, "partial inotify event received");
goto cleanup;
}
size = sizeof *eve + eve->len;
if( todo < size )
{
mce_log(LL_WARN, "oversized inotify event received");
goto cleanup;
}
#if DEBUG_INOTIFY_EVENTS
inotify_event_debug(eve);
#endif
if( eve->len && !strcmp(self->watch_file, eve->name) )
{
flg = TRUE;
}
if( eve->mask & IN_IGNORED )
{
mce_log(LL_ERR, "inotify watch went defunct");
flg = TRUE;
goto cleanup;
}
}
res = TRUE;
cleanup:
if( flg && self && self->changed_cb )
{
self->changed_cb(self->watch_path, self->watch_file, self->user_data);
}
return res;
}
/** Glib io glue for processing inotify event input
*
* @param source (not used)
* @param condition (not used)
* @param data pointer to filewatcher_t object (as void pointer)
*
* @return TRUE to keep the io watch alive, or
* FALSE if the io watch must be released
*/
static
gboolean
filewatcher_input_cb(GIOChannel *source,
GIOCondition condition,
gpointer data)
{
Mar 14, 2014
Mar 14, 2014
356
(void)source;
357
358
filewatcher_t *self = data;
Mar 14, 2014
Mar 14, 2014
359
360
361
362
363
364
365
366
367
368
369
gboolean keep_going = TRUE;
if( condition & (G_IO_ERR | G_IO_HUP | G_IO_NVAL) )
{
keep_going = FALSE;
}
if( !filewatcher_process_events(self) )
{
keep_going = FALSE;
}
370
371
372
if( !keep_going )
{
Mar 14, 2014
Mar 14, 2014
373
374
375
376
/* Note: This /should/ never happen, but if it does
* we must not leave the io watch in a state
* where it gets triggered forever. */
mce_log(LL_CRIT, "stopping inotify event io watch");
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
self->watch_id = 0;
}
return keep_going;
}
/** Helper for setting up inotify file descriptor
*
* @note This function is meant to be called form
* filewatcher_create() function only!
*
* @param self pointer to filewatcher_t object
*
* @return TRUE on success, or FALSE on failure
*/
static
gboolean
filewatcher_setup_inotify(filewatcher_t *self)
{
gboolean success = FALSE;
uint32_t mask = (0
| IN_CREATE
| IN_DELETE
| IN_CLOSE_WRITE
| IN_MOVED_TO
| IN_MOVED_FROM
| IN_DONT_FOLLOW
| IN_ONLYDIR);
self->inotify_fd = inotify_init1(IN_CLOEXEC);
if( self->inotify_fd == -1 )
{
mce_log(LL_WARN, "inotify_init: %m");
goto cleanup;
}
self->inotify_wd = inotify_add_watch(self->inotify_fd,
self->watch_path, mask);
if( self->inotify_wd == -1 )
{
mce_log(LL_WARN, "%s: inotify_add_watch: %m", self->watch_path);
goto cleanup;
}
success = TRUE;
cleanup:
return success;
}
/** Helper for setting up glib io watch for inotify file descriptor
*
* @note This function is meant to be called form
* filewatcher_create() function only!
*
* @param self pointer to filewatcher_t object
*
* @return TRUE on success, or FALSE on failure
*/
static
gboolean
filewatcher_setup_iowatch(filewatcher_t *self)
{
gboolean success = FALSE;
GIOChannel *chan = 0;
GError *err = 0;
if( !(chan = g_io_channel_unix_new(self->inotify_fd)) )
{
mce_log(LL_WARN, "%s: %m", "g_io_channel_unix_new");
goto cleanup;
}
/* the channel does not own the fd */
g_io_channel_set_close_on_unref(chan, FALSE);
/* Set to NULL encoding so that we can turn off the buffering */
if( g_io_channel_set_encoding(chan, NULL, &err) != G_IO_STATUS_NORMAL )
{
mce_log(LL_WARN, "%s: %s", "g_io_channel_set_encoding",
(err && err->message) ? err->message : "unknown");
}
g_io_channel_set_buffered(chan, FALSE);
Mar 14, 2014
Mar 14, 2014
463
self->watch_id = g_io_add_watch(chan,
May 13, 2014
May 13, 2014
464
465
G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
filewatcher_input_cb, self);
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
if( !self->watch_id )
{
mce_log(LL_WARN, "%s: %m", "g_io_add_watch");
goto cleanup;
}
success = TRUE;
cleanup:
g_clear_error(&err);
if( chan ) g_io_channel_unref(chan);
return success;
}
/** Create an filewatcher_t object
*
* An inotify watcher is started for the given director/file.
* A glib io watch is used to process the inotify events.
* The change_cb is called when contents of the tracked file
* are assumed to have changed.
*
* @note The change_cb function will not be called during the
* initialization. You can make initial state evaluation
* to happen by calling filewatcher_force_trigger() after
* succesfull filewatcher_create().
*
* @param dirpath directory to watch over
* @param filename file to watch in dirpath
* @param change_cb function to call when dirpath/filename changes
* @param user_data extra parameter to pass to change_cb
* @param delete_cb called on user_data when filewatcher_t itself is deleted
*
* @return pointer to filewatcher_t object, or NULL in case of errors
*/
filewatcher_t *
filewatcher_create(const char *dirpath,
const char *filename,
filewatcher_changed_fn change_cb,
gpointer user_data,
GDestroyNotify delete_cb)
{
gboolean success = FALSE;
filewatcher_t *self = g_malloc0(sizeof *self);
filewatcher_ctor(self);
self->watch_path = g_strdup(dirpath);
self->watch_file = g_strdup(filename);
self->changed_cb = change_cb;
self->user_data = user_data;
self->delete_cb = delete_cb;
if( !filewatcher_setup_inotify(self) )
{
goto cleanup;
}
if( !filewatcher_setup_iowatch(self) )
{
goto cleanup;
}
success = TRUE;
cleanup:
if( !success )
{
filewatcher_delete(self), self = 0;
}
return self;
}
/** Force calling the change notification callback
*
* This can be useful for example to feed initial
* state of tracked file via the same mechanism as
* the later changes get reported
*
* @param self pointer to filewatcher_t object
*/
void
filewatcher_force_trigger(filewatcher_t *self)
{
if( self->changed_cb )
{
self->changed_cb(self->watch_path, self->watch_file, self->user_data);
}
}