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
INotifyWatchTowerDriver()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
~INotifyWatchTowerDriver()41 INotifyWatchTowerDriver::~INotifyWatchTowerDriver()
42 {
43 close( breaking_pipe_read_fd_ );
44 close( breaking_pipe_write_fd_ );
45 }
46
addFile(const std::string & file_name)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
addSymlink(const std::string & file_name)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
addDir(const std::string & file_name)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
removeFile(const INotifyWatchTowerDriver::FileId & file_id)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
removeSymlink(const SymlinkId & symlink_id)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
removeDir(const DirId & dir_id)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*>
waitAndProcessEvents(INotifyObservedFileList * list,std::unique_lock<std::mutex> * list_lock,std::vector<INotifyObservedFile * > * files_needing_readding,int timeout_ms)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 LOG(logDEBUG) << "Read " << nb << " bytes";
140 if ( nb > 0 )
141 {
142 ssize_t offset = 0;
143 while ( offset < nb ) {
144 const inotify_event* event =
145 reinterpret_cast<const inotify_event*>( buffer + offset );
146
147 offset += processINotifyEvent( event, list,
148 &files_to_notify, files_needing_readding );
149 }
150 }
151 else
152 {
153 LOG(logWARNING) << "Error reading from inotify " << errno;
154 }
155 }
156
157 if ( fds[1].revents & POLLIN )
158 {
159 uint8_t byte;
160 read( breaking_pipe_read_fd_, &byte, sizeof byte );
161 }
162 }
163
164 return files_to_notify;
165 }
166
167 // Treats the passed event and returns the number of bytes used
processINotifyEvent(const struct inotify_event * event,INotifyObservedFileList * list,std::vector<INotifyObservedFile * > * files_to_notify,std::vector<INotifyObservedFile * > * files_needing_readding)168 size_t INotifyWatchTowerDriver::processINotifyEvent(
169 const struct inotify_event* event,
170 INotifyObservedFileList* list,
171 std::vector<INotifyObservedFile*>* files_to_notify,
172 std::vector<INotifyObservedFile*>* files_needing_readding )
173 {
174 LOG(logDEBUG) << "Event received: 0x" << std::hex << event->mask;
175
176 INotifyObservedFile* file = nullptr;
177
178 if ( event->mask & ( IN_MODIFY | IN_DELETE_SELF | IN_MOVE_SELF ) )
179 {
180 LOG(logDEBUG) << "IN_MODIFY | IN_DELETE_SELF | IN_MOVE_SELF for wd " << event->wd;
181
182 // Retrieve the file
183 file = list->searchByFileOrSymlinkWd(
184 { event->wd }, { event->wd } );
185 }
186 else if ( event->mask & ( IN_CREATE | IN_MOVED_TO | IN_MOVED_FROM ) )
187 {
188 LOG(logDEBUG) << "IN_CREATE | IN_MOVED_TO | IN_MOVED_FROM for wd " << event->wd
189 << " name: " << event->name;
190
191 // Retrieve the file
192 file = list->searchByDirWdAndName( { event->wd }, event->name );
193
194 if ( file )
195 {
196 LOG(logDEBUG) << "Dir change for watched file " << event->name;
197 files_needing_readding->push_back( file );
198 }
199 }
200 else
201 {
202 LOG(logDEBUG) << "Unexpected event: " << event->mask << " wd " << event->wd;
203 }
204
205 if ( file )
206 {
207 LOG(logDEBUG) << "Adding file: " << std::hex << file;
208 files_to_notify->push_back( file );
209 }
210
211 return sizeof( struct inotify_event ) + event->len;
212 }
213
interruptWait()214 void INotifyWatchTowerDriver::interruptWait()
215 {
216 char byte = 'X';
217
218 (void) write( breaking_pipe_write_fd_, (void*) &byte, sizeof byte );
219 }
220