1
2 /// inotify C++ interface implementation
3 /**
4 * \file inotify-cxx.cpp
5 *
6 * inotify C++ interface
7 *
8 * Copyright (C) 2006, 2007, 2008, 2012 Lukas Jelinek <lukas@aiken.cz>
9 *
10 * This program is free software; you can redistribute it and/or
11 * modify it under the terms of one of the following licenses:
12 *
13 * \li 1. X11-style license (see LICENSE-X11)
14 * \li 2. GNU Lesser General Public License, version 2.1 (see LICENSE-LGPL)
15 * \li 3. GNU General Public License, version 2 (see LICENSE-GPL)
16 *
17 * If you want to help with choosing the best license for you,
18 * please visit http://www.gnu.org/licenses/license-list.html.
19 *
20 * Credits:
21 * Christian Ruppert (new include to build with GCC 4.4+)
22 *
23 */
24
25
26 #include <errno.h>
27 #include <unistd.h>
28 #include <fcntl.h>
29 #include <cstdio>
30 //#include <syslog.h> // TODO remove
31
32 #include "inotify-cxx.h"
33 #pragma GCC diagnostic ignored "-Wpedantic" // inotify-cxx is not pedantic
34
35 /// procfs inotify base path
36 #define PROCFS_INOTIFY_BASE "/proc/sys/fs/inotify/"
37
38 /// dump separator (between particular entries)
39 #define DUMP_SEP \
40 ({ \
41 if (!rStr.empty()) { \
42 rStr.append(","); \
43 } \
44 })
45
46
47
GetDescriptor() const48 int32_t InotifyEvent::GetDescriptor() const
49 {
50 return m_pWatch != NULL // if watch exists
51 ? m_pWatch->GetDescriptor() // return its descriptor
52 : -1; // else return -1
53 }
54
GetMaskByName(const std::string & rName)55 uint32_t InotifyEvent::GetMaskByName(const std::string& rName)
56 {
57 if (rName == "IN_ACCESS")
58 return IN_ACCESS;
59 else if (rName == "IN_MODIFY")
60 return IN_MODIFY;
61 else if (rName == "IN_ATTRIB")
62 return IN_ATTRIB;
63 else if (rName == "IN_CLOSE_WRITE")
64 return IN_CLOSE_WRITE;
65 else if (rName == "IN_CLOSE_NOWRITE")
66 return IN_CLOSE_NOWRITE;
67 else if (rName == "IN_OPEN")
68 return IN_OPEN;
69 else if (rName == "IN_MOVED_FROM")
70 return IN_MOVED_FROM;
71 else if (rName == "IN_MOVED_TO")
72 return IN_MOVED_TO;
73 else if (rName == "IN_CREATE")
74 return IN_CREATE;
75 else if (rName == "IN_DELETE")
76 return IN_DELETE;
77 else if (rName == "IN_DELETE_SELF")
78 return IN_DELETE_SELF;
79 else if (rName == "IN_UNMOUNT")
80 return IN_UNMOUNT;
81 else if (rName == "IN_Q_OVERFLOW")
82 return IN_Q_OVERFLOW;
83 else if (rName == "IN_IGNORED")
84 return IN_IGNORED;
85 else if (rName == "IN_CLOSE")
86 return IN_CLOSE;
87 else if (rName == "IN_MOVE")
88 return IN_MOVE;
89 else if (rName == "IN_ISDIR")
90 return IN_ISDIR;
91 else if (rName == "IN_ONESHOT")
92 return IN_ONESHOT;
93 else if (rName == "IN_ALL_EVENTS")
94 return IN_ALL_EVENTS;
95
96 #ifdef IN_DONT_FOLLOW
97 else if (rName == "IN_DONT_FOLLOW")
98 return IN_DONT_FOLLOW;
99 #endif // IN_DONT_FOLLOW
100
101 #ifdef IN_ONLYDIR
102 else if (rName == "IN_ONLYDIR")
103 return IN_ONLYDIR;
104 #endif // IN_ONLYDIR
105
106 #ifdef IN_MOVE_SELF
107 else if (rName == "IN_MOVE_SELF")
108 return IN_MOVE_SELF;
109 #endif // IN_MOVE_SELF
110
111 return (uint32_t) 0;
112 }
113
DumpTypes(uint32_t uValue,std::string & rStr)114 void InotifyEvent::DumpTypes(uint32_t uValue, std::string& rStr)
115 {
116 rStr = "";
117
118 if (IsType(uValue, IN_ALL_EVENTS)) {
119 rStr.append("IN_ALL_EVENTS");
120 }
121 else {
122 if (IsType(uValue, IN_ACCESS)) {
123 DUMP_SEP;
124 rStr.append("IN_ACCESS");
125 }
126 if (IsType(uValue, IN_MODIFY)) {
127 DUMP_SEP;
128 rStr.append("IN_MODIFY");
129 }
130 if (IsType(uValue, IN_ATTRIB)) {
131 DUMP_SEP;
132 rStr.append("IN_ATTRIB");
133 }
134 if (IsType(uValue, IN_CREATE)) {
135 DUMP_SEP;
136 rStr.append("IN_CREATE");
137 }
138 if (IsType(uValue, IN_DELETE)) {
139 DUMP_SEP;
140 rStr.append("IN_DELETE");
141 }
142 if (IsType(uValue, IN_DELETE_SELF)) {
143 DUMP_SEP;
144 rStr.append("IN_DELETE_SELF");
145 }
146 if (IsType(uValue, IN_OPEN)) {
147 DUMP_SEP;
148 rStr.append("IN_OPEN");
149 }
150 if (IsType(uValue, IN_CLOSE)) {
151 DUMP_SEP;
152 rStr.append("IN_CLOSE");
153 }
154
155 #ifdef IN_MOVE_SELF
156 if (IsType(uValue, IN_MOVE_SELF)) {
157 DUMP_SEP;
158 rStr.append("IN_MOVE_SELF");
159 }
160 #endif // IN_MOVE_SELF
161
162 else {
163 if (IsType(uValue, IN_CLOSE_WRITE)) {
164 DUMP_SEP;
165 rStr.append("IN_CLOSE_WRITE");
166 }
167 if (IsType(uValue, IN_CLOSE_NOWRITE)) {
168 DUMP_SEP;
169 rStr.append("IN_CLOSE_NOWRITE");
170 }
171 }
172 if (IsType(uValue, IN_MOVE)) {
173 DUMP_SEP;
174 rStr.append("IN_MOVE");
175 }
176 else {
177 if (IsType(uValue, IN_MOVED_FROM)) {
178 DUMP_SEP;
179 rStr.append("IN_MOVED_FROM");
180 }
181 if (IsType(uValue, IN_MOVED_TO)) {
182 DUMP_SEP;
183 rStr.append("IN_MOVED_TO");
184 }
185 }
186 }
187 if (IsType(uValue, IN_UNMOUNT)) {
188 DUMP_SEP;
189 rStr.append("IN_UNMOUNT");
190 }
191 if (IsType(uValue, IN_Q_OVERFLOW)) {
192 DUMP_SEP;
193 rStr.append("IN_Q_OVERFLOW");
194 }
195 if (IsType(uValue, IN_IGNORED)) {
196 DUMP_SEP;
197 rStr.append("IN_IGNORED");
198 }
199 if (IsType(uValue, IN_ISDIR)) {
200 DUMP_SEP;
201 rStr.append("IN_ISDIR");
202 }
203 if (IsType(uValue, IN_ONESHOT)) {
204 DUMP_SEP;
205 rStr.append("IN_ONESHOT");
206 }
207
208 #ifdef IN_DONT_FOLLOW
209 if (IsType(uValue, IN_DONT_FOLLOW)) {
210 DUMP_SEP;
211 rStr.append("IN_DONT_FOLLOW");
212 }
213 #endif // IN_DONT_FOLLOW
214
215 #ifdef IN_ONLYDIR
216 if (IsType(uValue, IN_ONLYDIR)) {
217 DUMP_SEP;
218 rStr.append("IN_ONLYDIR");
219 }
220 #endif // IN_ONLYDIR
221 }
222
DumpTypes(std::string & rStr) const223 void InotifyEvent::DumpTypes(std::string& rStr) const
224 {
225 DumpTypes(m_uMask, rStr);
226 }
227
228
SetMask(uint32_t uMask)229 void InotifyWatch::SetMask(uint32_t uMask) throw (InotifyException)
230 {
231 IN_WRITE_BEGIN
232
233 if (m_wd != -1) {
234 int wd = inotify_add_watch(m_pInotify->GetDescriptor(), m_path.c_str(), uMask);
235 if (wd != m_wd) {
236 IN_WRITE_END_NOTHROW
237 throw InotifyException(IN_EXC_MSG("changing mask failed"), wd == -1 ? errno : EINVAL, this);
238 }
239 }
240
241 m_uMask = uMask;
242
243 IN_WRITE_END
244 }
245
SetEnabled(bool fEnabled)246 void InotifyWatch::SetEnabled(bool fEnabled) throw (InotifyException)
247 {
248 //syslog(LOG_INFO, "(SetEnabled) NOW (%i)", fEnabled);
249 IN_WRITE_BEGIN
250
251 if (fEnabled == m_fEnabled) {
252 IN_WRITE_END_NOTHROW
253 return;
254 }
255
256 if (m_pInotify != NULL) {
257 if (fEnabled) {
258 m_wd = inotify_add_watch(m_pInotify->GetDescriptor(), m_path.c_str(), m_uMask);
259 if (m_wd == -1) {
260 IN_WRITE_END_NOTHROW
261 throw InotifyException(IN_EXC_MSG("enabling watch failed"), errno, this);
262 }
263 m_pInotify->m_watches.insert(IN_WATCH_MAP::value_type(m_wd, this));
264 }
265 else {
266 if (inotify_rm_watch(m_pInotify->GetDescriptor(), m_wd) != 0) {
267 IN_WRITE_END_NOTHROW
268 throw InotifyException(IN_EXC_MSG("disabling watch failed"), errno, this);
269 }
270 m_pInotify->m_watches.erase(m_wd);
271 m_wd = -1;
272 }
273 }
274
275 m_fEnabled = fEnabled;
276
277 IN_WRITE_END
278 //syslog(LOG_INFO, "(SetEnabled) END (%i)", fEnabled);
279 }
280
__Disable()281 void InotifyWatch::__Disable()
282 {
283 IN_WRITE_BEGIN
284
285 if (!m_fEnabled) {
286 IN_WRITE_END_NOTHROW
287 throw InotifyException(IN_EXC_MSG("event cannot occur on disabled watch"), EINVAL, this);
288 }
289
290 if (m_pInotify != NULL) {
291 m_pInotify->m_watches.erase(m_wd);
292 m_wd = -1;
293 }
294
295 m_fEnabled = false;
296
297 IN_WRITE_END
298 }
299
300
Inotify()301 Inotify::Inotify() throw (InotifyException)
302 {
303 IN_LOCK_INIT
304
305 m_fd = inotify_init();
306 if (m_fd == -1) {
307 IN_LOCK_DONE
308 throw InotifyException(IN_EXC_MSG("inotify init failed"), errno, NULL);
309 }
310 }
311
~Inotify()312 Inotify::~Inotify()
313 {
314 Close();
315
316 IN_LOCK_DONE
317 }
318
Close()319 void Inotify::Close()
320 {
321 IN_WRITE_BEGIN
322
323 if (m_fd != -1) {
324 RemoveAll();
325 close(m_fd);
326 m_fd = -1;
327 }
328
329 IN_WRITE_END
330 }
331
Add(InotifyWatch * pWatch)332 void Inotify::Add(InotifyWatch* pWatch) throw (InotifyException)
333 {
334 IN_WRITE_BEGIN
335
336 // invalid descriptor - this case shouldn't occur - go away
337 if (m_fd == -1) {
338 IN_WRITE_END_NOTHROW
339 throw InotifyException(IN_EXC_MSG("invalid file descriptor"), EBUSY, this);
340 }
341
342 // this path already watched - go away
343 if (FindWatch(pWatch->GetPath()) != NULL) {
344 IN_WRITE_END_NOTHROW
345 throw InotifyException(IN_EXC_MSG("path already watched"), EBUSY, this);
346 }
347
348 // for enabled watch
349 if (pWatch->IsEnabled()) {
350
351 // try to add watch to kernel
352 int wd = inotify_add_watch(m_fd, pWatch->GetPath().c_str(), pWatch->GetMask());
353
354 // adding failed - go away
355 if (wd == -1) {
356 IN_WRITE_END_NOTHROW
357 throw InotifyException(IN_EXC_MSG("adding watch failed"), errno, this);
358 }
359
360 // this path already watched (but defined another way)
361 InotifyWatch* pW = FindWatch(wd);
362 if (pW != NULL) {
363
364 // try to recover old watch because it may be modified - then go away
365 if (inotify_add_watch(m_fd, pW->GetPath().c_str(), pW->GetMask()) < 0) {
366 IN_WRITE_END_NOTHROW
367 throw InotifyException(IN_EXC_MSG("watch collision detected and recovery failed"), errno, this);
368 }
369 else {
370 // recovery failed - go away
371 IN_WRITE_END_NOTHROW
372 throw InotifyException(IN_EXC_MSG("path already watched (but defined another way)"), EBUSY, this);
373 }
374 }
375
376 pWatch->m_wd = wd;
377 m_watches.insert(IN_WATCH_MAP::value_type(pWatch->m_wd, pWatch));
378 }
379
380 m_paths.insert(IN_WP_MAP::value_type(pWatch->m_path, pWatch));
381 pWatch->m_pInotify = this;
382
383 IN_WRITE_END
384 }
385
Remove(InotifyWatch * pWatch)386 void Inotify::Remove(InotifyWatch* pWatch) throw (InotifyException)
387 {
388 IN_WRITE_BEGIN
389
390 // invalid descriptor - this case shouldn't occur - go away
391 if (m_fd == -1) {
392 IN_WRITE_END_NOTHROW
393 throw InotifyException(IN_EXC_MSG("invalid file descriptor"), EBUSY, this);
394 }
395
396 // for enabled watch
397 if (pWatch->m_wd != -1) {
398
399 // removing watch failed - go away
400 if (inotify_rm_watch(m_fd, pWatch->m_wd) == -1) {
401 IN_WRITE_END_NOTHROW
402 throw InotifyException(IN_EXC_MSG("removing watch failed"), errno, this);
403 }
404 m_watches.erase(pWatch->m_wd);
405 pWatch->m_wd = -1;
406 }
407
408 m_paths.erase(pWatch->m_path);
409 pWatch->m_pInotify = NULL;
410
411 IN_WRITE_END
412 }
413
RemoveAll()414 void Inotify::RemoveAll()
415 {
416 IN_WRITE_BEGIN
417
418 IN_WP_MAP::iterator it = m_paths.begin();
419 while (it != m_paths.end()) {
420 InotifyWatch* pW = (*it).second;
421 if (pW->m_wd != -1) {
422 inotify_rm_watch(m_fd, pW->m_wd);
423 pW->m_wd = -1;
424 }
425 pW->m_pInotify = NULL;
426 it++;
427 }
428
429 m_watches.clear();
430 m_paths.clear();
431
432 IN_WRITE_END
433 }
434
WaitForEvents(bool fNoIntr)435 void Inotify::WaitForEvents(bool fNoIntr) throw (InotifyException)
436 {
437 ssize_t len = 0;
438
439 do {
440 len = read(m_fd, m_buf, INOTIFY_BUFLEN);
441 } while (fNoIntr && len == -1 && errno == EINTR);
442
443 if (len == -1 && !(errno == EWOULDBLOCK || errno == EINTR))
444 throw InotifyException(IN_EXC_MSG("reading events failed"), errno, this);
445
446 if (len == -1)
447 return;
448
449 IN_WRITE_BEGIN
450
451 ssize_t i = 0;
452 while (i < len) {
453 struct inotify_event* pEvt = (struct inotify_event*) &m_buf[i];
454 InotifyWatch* pW = FindWatch(pEvt->wd);
455 if (pW != NULL) {
456 InotifyEvent evt(pEvt, pW);
457 if ( InotifyEvent::IsType(pW->GetMask(), IN_ONESHOT)
458 || InotifyEvent::IsType(evt.GetMask(), IN_IGNORED))
459 pW->__Disable();
460 m_events.push_back(evt);
461 }
462 i += INOTIFY_EVENT_SIZE + (ssize_t) pEvt->len;
463 }
464
465 IN_WRITE_END
466 }
467
GetEvent(InotifyEvent * pEvt)468 bool Inotify::GetEvent(InotifyEvent* pEvt) throw (InotifyException)
469 {
470 if (pEvt == NULL)
471 throw InotifyException(IN_EXC_MSG("null pointer to event"), EINVAL, this);
472
473 IN_WRITE_BEGIN
474
475 bool b = !m_events.empty();
476 if (b) {
477 *pEvt = m_events.front();
478 m_events.pop_front();
479 }
480
481 IN_WRITE_END
482
483 return b;
484 }
485
PeekEvent(InotifyEvent * pEvt)486 bool Inotify::PeekEvent(InotifyEvent* pEvt) throw (InotifyException)
487 {
488 if (pEvt == NULL)
489 throw InotifyException(IN_EXC_MSG("null pointer to event"), EINVAL, this);
490
491 IN_READ_BEGIN
492
493 bool b = !m_events.empty();
494 if (b) {
495 *pEvt = m_events.front();
496 }
497
498 IN_READ_END
499
500 return b;
501 }
502
FindWatch(int iDescriptor)503 InotifyWatch* Inotify::FindWatch(int iDescriptor)
504 {
505 IN_READ_BEGIN
506
507 IN_WATCH_MAP::iterator it = m_watches.find(iDescriptor);
508 InotifyWatch* pW = it == m_watches.end() ? NULL : (*it).second;
509
510 IN_READ_END
511
512 return pW;
513 }
514
FindWatch(const std::string & rPath)515 InotifyWatch* Inotify::FindWatch(const std::string& rPath)
516 {
517 IN_READ_BEGIN
518
519 IN_WP_MAP::iterator it = m_paths.find(rPath);
520 InotifyWatch* pW = it == m_paths.end() ? NULL : (*it).second;
521
522 IN_READ_END
523
524 return pW;
525 }
526
SetNonBlock(bool fNonBlock)527 void Inotify::SetNonBlock(bool fNonBlock) throw (InotifyException)
528 {
529 IN_WRITE_BEGIN
530
531 if (m_fd == -1) {
532 IN_WRITE_END_NOTHROW
533 throw InotifyException(IN_EXC_MSG("invalid file descriptor"), EBUSY, this);
534 }
535
536 int res = fcntl(m_fd, F_GETFL);
537 if (res == -1) {
538 IN_WRITE_END_NOTHROW
539 throw InotifyException(IN_EXC_MSG("cannot get inotify flags"), errno, this);
540 }
541
542 if (fNonBlock) {
543 res |= O_NONBLOCK;
544 }
545 else {
546 res &= ~O_NONBLOCK;
547 }
548
549 if (fcntl(m_fd, F_SETFL, res) == -1) {
550 IN_WRITE_END_NOTHROW
551 throw InotifyException(IN_EXC_MSG("cannot set inotify flags"), errno, this);
552 }
553
554 IN_WRITE_END
555 }
556
SetCloseOnExec(bool fClOnEx)557 void Inotify::SetCloseOnExec(bool fClOnEx) throw (InotifyException)
558 {
559 IN_WRITE_BEGIN
560
561 if (m_fd == -1) {
562 IN_WRITE_END_NOTHROW
563 throw InotifyException(IN_EXC_MSG("invalid file descriptor"), EBUSY, this);
564 }
565
566 int res = fcntl(m_fd, F_GETFD);
567 if (res == -1) {
568 IN_WRITE_END_NOTHROW
569 throw InotifyException(IN_EXC_MSG("cannot get inotify flags"), errno, this);
570 }
571
572 if (fClOnEx) {
573 res |= FD_CLOEXEC;
574 }
575 else {
576 res &= ~FD_CLOEXEC;
577 }
578
579 if (fcntl(m_fd, F_SETFD, res) == -1) {
580 IN_WRITE_END_NOTHROW
581 throw InotifyException(IN_EXC_MSG("cannot set inotify flags"), errno, this);
582 }
583
584 IN_WRITE_END
585 }
586
GetCapability(InotifyCapability_t cap)587 uint32_t Inotify::GetCapability(InotifyCapability_t cap) throw (InotifyException)
588 {
589 FILE* f = fopen(GetCapabilityPath(cap).c_str(), "r");
590 if (f == NULL)
591 throw InotifyException(IN_EXC_MSG("cannot get capability"), errno, NULL);
592
593 unsigned int val = 0;
594 if (fscanf(f, "%u", &val) != 1) {
595 fclose(f);
596 throw InotifyException(IN_EXC_MSG("cannot get capability"), EIO, NULL);
597 }
598
599 fclose(f);
600
601 return (uint32_t) val;
602 }
603
SetCapability(InotifyCapability_t cap,uint32_t val)604 void Inotify::SetCapability(InotifyCapability_t cap, uint32_t val) throw (InotifyException)
605 {
606 FILE* f = fopen(GetCapabilityPath(cap).c_str(), "w");
607 if (f == NULL)
608 throw InotifyException(IN_EXC_MSG("cannot set capability"), errno, NULL);
609
610 if (fprintf(f, "%u", (unsigned int) val) <= 0) {
611 fclose(f);
612 throw InotifyException(IN_EXC_MSG("cannot set capability"), EIO, NULL);
613 }
614
615 fclose(f);
616 }
617
GetCapabilityPath(InotifyCapability_t cap)618 std::string Inotify::GetCapabilityPath(InotifyCapability_t cap) throw (InotifyException)
619 {
620 std::string path(PROCFS_INOTIFY_BASE);
621
622 switch (cap) {
623 case IN_MAX_EVENTS:
624 path.append("max_queued_events");
625 break;
626 case IN_MAX_INSTANCES:
627 path.append("max_user_instances");
628 break;
629 case IN_MAX_WATCHES:
630 path.append("max_user_watches");
631 break;
632 default:
633 throw InotifyException(IN_EXC_MSG("unknown capability type"), EINVAL, NULL);
634 }
635
636 return path;
637 }
638
639 #pragma GCC diagnostic warning "-Wpedantic"
640