xref: /glogg/src/inotifywatchtowerdriver.cpp (revision 702af59ea138e3124b906092de415e3601c74d3e)
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         int timeout_ms )
114 {
115     std::vector<INotifyObservedFile*> files_to_notify;
116     struct pollfd fds[2];
117 
118     fds[0].fd      = inotify_fd_;
119     fds[0].events  = POLLIN;
120     fds[0].revents = 0;
121 
122     fds[1].fd      = breaking_pipe_read_fd_;
123     fds[1].events  = POLLIN;
124     fds[1].revents = 0;
125 
126     list_lock->unlock();
127     int poll_ret = poll( fds, 2, timeout_ms ? timeout_ms : -1 );
128     list_lock->lock();
129 
130     if ( poll_ret > 0 )
131     {
132         if ( fds[0].revents & POLLIN )
133         {
134             LOG(logDEBUG) << "Pollin for inotify";
135             char buffer[ INOTIFY_BUFFER_SIZE ]
136                 __attribute__ ((aligned(__alignof__(struct inotify_event))));
137 
138             ssize_t nb = read( inotify_fd_, buffer, sizeof( buffer ) );
139             if ( nb > 0 )
140             {
141                 ssize_t offset = 0;
142                 while ( offset < nb ) {
143                     const inotify_event* event =
144                         reinterpret_cast<const inotify_event*>( buffer + offset );
145 
146                     offset += processINotifyEvent( event, list,
147                             &files_to_notify, files_needing_readding );
148                 }
149             }
150             else
151             {
152                 LOG(logWARNING) << "Error reading from inotify " << errno;
153             }
154         }
155 
156         if ( fds[1].revents & POLLIN )
157         {
158             uint8_t byte;
159             read( breaking_pipe_read_fd_, &byte, sizeof byte );
160         }
161     }
162 
163     return files_to_notify;
164 }
165 
166 // Treats the passed event and returns the number of bytes used
167 size_t INotifyWatchTowerDriver::processINotifyEvent(
168         const struct inotify_event* event,
169         INotifyObservedFileList* list,
170         std::vector<INotifyObservedFile*>* files_to_notify,
171         std::vector<INotifyObservedFile*>* files_needing_readding )
172 {
173     LOG(logDEBUG) << "Event received: " << std::hex << event->mask;
174 
175     INotifyObservedFile* file = nullptr;
176 
177     if ( event->mask & ( IN_MODIFY | IN_DELETE_SELF | IN_MOVE_SELF ) )
178     {
179         LOG(logDEBUG) << "IN_MODIFY | IN_DELETE_SELF | IN_MOVE_SELF for wd " << event->wd;
180 
181         // Retrieve the file
182         file = list->searchByFileOrSymlinkWd(
183                 { event->wd }, { event->wd } );
184     }
185     else if ( event->mask & ( IN_CREATE | IN_MOVED_TO | IN_MOVED_FROM ) )
186     {
187         LOG(logDEBUG) << "IN_CREATE | IN_MOVED_TO | IN_MOVED_FROM for wd " << event->wd
188             << " name: " << event->name;
189 
190         // Retrieve the file
191         file = list->searchByDirWdAndName( { event->wd }, event->name );
192 
193         if ( file )
194         {
195             LOG(logDEBUG) << "Dir change for watched file " << event->name;
196             files_needing_readding->push_back( file );
197         }
198     }
199     else
200     {
201         LOG(logDEBUG) << "Unexpected event: " << event->mask << " wd " << event->wd;
202     }
203 
204     if ( file )
205     {
206         LOG(logDEBUG) << "Adding file: " << std::hex << file;
207         files_to_notify->push_back( file );
208     }
209 
210     return sizeof( struct inotify_event ) + event->len;
211 }
212 
213 void INotifyWatchTowerDriver::interruptWait()
214 {
215     char byte = 'X';
216 
217     (void) write( breaking_pipe_write_fd_, (void*) &byte, sizeof byte );
218 }
219