xref: /glogg/src/inotifywatchtowerdriver.cpp (revision 9ebe83d51f785b947e84c5b8993afea9a272ffd5)
1 /*
2  * Copyright (C) 2015 Nicolas Bonnefon and other contributors
3  *
4  * This file is part of glogg.
5  *
6  * glogg 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  * glogg 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 glogg.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #include "inotifywatchtowerdriver.h"
21 
22 #include <sys/inotify.h>
23 #include <poll.h>
24 #include <unistd.h>
25 #include <fcntl.h>
26 
27 #include "log.h"
28 
29 #include "watchtowerlist.h"
30 
31 INotifyWatchTowerDriver::INotifyWatchTowerDriver() : inotify_fd_( inotify_init() )
32 {
33     int pipefd[2];
34 
35     pipe2( pipefd, O_NONBLOCK );
36 
37     breaking_pipe_read_fd_  = pipefd[0];
38     breaking_pipe_write_fd_ = pipefd[1];
39 }
40 
41 INotifyWatchTowerDriver::~INotifyWatchTowerDriver()
42 {
43     close( breaking_pipe_read_fd_ );
44     close( breaking_pipe_write_fd_ );
45 }
46 
47 INotifyWatchTowerDriver::FileId INotifyWatchTowerDriver::addFile(
48         const std::string& file_name )
49 {
50     // Add a watch for the inode
51     int wd = inotify_add_watch( inotify_fd_, file_name.c_str(),
52             IN_DELETE_SELF | IN_MODIFY | IN_MOVE_SELF );
53 
54     LOG(logDEBUG) << "INotifyWatchTower::addFile new inotify wd " << wd;
55 
56     return { wd };
57 }
58 
59 INotifyWatchTowerDriver::SymlinkId INotifyWatchTowerDriver::addSymlink(
60         const std::string& file_name )
61 {
62     int symlink_wd = inotify_add_watch( inotify_fd_, file_name.c_str(),
63             IN_DONT_FOLLOW | IN_DELETE_SELF | IN_MODIFY | IN_MOVE_SELF );
64     LOG(logDEBUG) << "INotifyWatchTower::addFile new inotify symlink_wd " << symlink_wd;
65     // (not sure a symlink can be modified but you never know)
66 
67     return { symlink_wd };
68 }
69 
70 INotifyWatchTowerDriver::DirId INotifyWatchTowerDriver::addDir(
71         const std::string& file_name )
72 {
73     int dir_wd = inotify_add_watch( inotify_fd_, file_name.c_str(),
74             IN_CREATE | IN_MOVE | IN_ONLYDIR );
75     LOG(logDEBUG) << "INotifyWatchTower::addFile dir " << file_name
76         << " watched wd " << dir_wd;
77 
78     return { dir_wd };
79 }
80 
81 void INotifyWatchTowerDriver::removeFile(
82         const INotifyWatchTowerDriver::FileId& file_id )
83 {
84     /*
85        LOG(logDEBUG) << "INotifyWatchTower::removeNotification removing inotify wd "
86        << file->file_wd_ << " symlink_wd " << file->symlink_wd_;
87        */
88     if ( file_id.wd_ >= 0 )
89         inotify_rm_watch( inotify_fd_, file_id.wd_ );
90 }
91 
92 void INotifyWatchTowerDriver::removeSymlink( const SymlinkId& symlink_id )
93 {
94     if ( symlink_id.wd_ >= 0 )
95         inotify_rm_watch( inotify_fd_, symlink_id.wd_ );
96 }
97 
98 void INotifyWatchTowerDriver::removeDir( const DirId& dir_id )
99 {
100    LOG(logDEBUG) << "INotifyWatchTower::removeDir removing inotify wd " << dir_id.wd_;
101 
102     if ( dir_id.wd_ >= 0 )
103         inotify_rm_watch( inotify_fd_, dir_id.wd_ );
104 }
105 
106 static constexpr size_t INOTIFY_BUFFER_SIZE = 4096;
107 
108 std::vector<INotifyWatchTowerDriver::INotifyObservedFile*>
109 INotifyWatchTowerDriver::waitAndProcessEvents(
110         INotifyObservedFileList* list,
111         std::unique_lock<std::mutex>* list_lock,
112         std::vector<INotifyObservedFile*>* files_needing_readding )
113 {
114     std::vector<INotifyObservedFile*> files_to_notify;
115     struct pollfd fds[2];
116 
117     fds[0].fd      = inotify_fd_;
118     fds[0].events  = POLLIN;
119     fds[0].revents = 0;
120 
121     fds[1].fd      = breaking_pipe_read_fd_;
122     fds[1].events  = POLLIN;
123     fds[1].revents = 0;
124 
125     list_lock->unlock();
126     int poll_ret = poll( fds, 2, -1 );
127     list_lock->lock();
128 
129     if ( poll_ret > 0 )
130     {
131         if ( fds[0].revents & POLLIN )
132         {
133             LOG(logDEBUG) << "Pollin for inotify";
134             char buffer[ INOTIFY_BUFFER_SIZE ]
135                 __attribute__ ((aligned(__alignof__(struct inotify_event))));
136 
137             ssize_t nb = read( inotify_fd_, buffer, sizeof( buffer ) );
138             if ( nb > 0 )
139             {
140                 ssize_t offset = 0;
141                 while ( offset < nb ) {
142                     const inotify_event* event =
143                         reinterpret_cast<const inotify_event*>( buffer + offset );
144 
145                     offset += processINotifyEvent( event, list,
146                             &files_to_notify, files_needing_readding );
147                 }
148             }
149             else
150             {
151                 LOG(logWARNING) << "Error reading from inotify " << errno;
152             }
153         }
154 
155         if ( fds[1].revents & POLLIN )
156         {
157             uint8_t byte;
158             read( breaking_pipe_read_fd_, &byte, sizeof byte );
159         }
160     }
161 
162     return files_to_notify;
163 }
164 
165 // Treats the passed event and returns the number of bytes used
166 size_t INotifyWatchTowerDriver::processINotifyEvent(
167         const struct inotify_event* event,
168         INotifyObservedFileList* list,
169         std::vector<INotifyObservedFile*>* files_to_notify,
170         std::vector<INotifyObservedFile*>* files_needing_readding )
171 {
172     LOG(logDEBUG) << "Event received: " << std::hex << event->mask;
173 
174     INotifyObservedFile* file = nullptr;
175 
176     if ( event->mask & ( IN_MODIFY | IN_DELETE_SELF | IN_MOVE_SELF ) )
177     {
178         LOG(logDEBUG) << "IN_MODIFY | IN_DELETE_SELF | IN_MOVE_SELF for wd " << event->wd;
179 
180         // Retrieve the file
181         file = list->searchByFileOrSymlinkWd(
182                 { event->wd }, { event->wd } );
183     }
184     else if ( event->mask & ( IN_CREATE | IN_MOVED_TO | IN_MOVED_FROM ) )
185     {
186         LOG(logDEBUG) << "IN_CREATE | IN_MOVED_TO | IN_MOVED_FROM for wd " << event->wd
187             << " name: " << event->name;
188 
189         // Retrieve the file
190         file = list->searchByDirWdAndName( { event->wd }, event->name );
191 
192         if ( file )
193         {
194             LOG(logDEBUG) << "Dir change for watched file " << event->name;
195             files_needing_readding->push_back( file );
196         }
197     }
198     else
199     {
200         LOG(logDEBUG) << "Unexpected event: " << event->mask << " wd " << event->wd;
201     }
202 
203     if ( file )
204     {
205         LOG(logDEBUG) << "Adding file: " << std::hex << file;
206         files_to_notify->push_back( file );
207     }
208 
209     return sizeof( struct inotify_event ) + event->len;
210 }
211 
212 void INotifyWatchTowerDriver::interruptWait()
213 {
214     char byte = 'X';
215 
216     (void) write( breaking_pipe_write_fd_, (void*) &byte, sizeof byte );
217 }
218