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