Qt 4.8
qfilesystemwatcher_kqueue.cpp
Go to the documentation of this file.
1 /****************************************************************************
2 **
3 ** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/legal
5 **
6 ** This file is part of the QtCore module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and Digia. For licensing terms and
14 ** conditions see http://qt.digia.com/licensing. For further information
15 ** use the contact form at http://qt.digia.com/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 2.1 requirements
23 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24 **
25 ** In addition, as a special exception, Digia gives you certain additional
26 ** rights. These rights are described in the Digia Qt LGPL Exception
27 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
28 **
29 ** GNU General Public License Usage
30 ** Alternatively, this file may be used under the terms of the GNU
31 ** General Public License version 3.0 as published by the Free Software
32 ** Foundation and appearing in the file LICENSE.GPL included in the
33 ** packaging of this file. Please review the following information to
34 ** ensure the GNU General Public License version 3.0 requirements will be
35 ** met: http://www.gnu.org/copyleft/gpl.html.
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41 
42 #include <qplatformdefs.h>
43 
44 #include "qfilesystemwatcher.h"
46 #include "private/qcore_unix_p.h"
47 
48 #ifndef QT_NO_FILESYSTEMWATCHER
49 
50 #include <qdebug.h>
51 #include <qfile.h>
52 #include <qsocketnotifier.h>
53 #include <qvarlengtharray.h>
54 
55 #include <sys/types.h>
56 #include <sys/event.h>
57 #include <sys/stat.h>
58 #include <sys/time.h>
59 #include <fcntl.h>
60 
62 
63 // #define KEVENT_DEBUG
64 #ifdef KEVENT_DEBUG
65 # define DEBUG qDebug
66 #else
67 # define DEBUG if(false)qDebug
68 #endif
69 
71 {
72  int kqfd = kqueue();
73  if (kqfd == -1)
74  return 0;
75  return new QKqueueFileSystemWatcherEngine(kqfd);
76 }
77 
79  : kqfd(kqfd)
80 {
81  fcntl(kqfd, F_SETFD, FD_CLOEXEC);
82 
83  if (pipe(kqpipe) == -1) {
84  perror("QKqueueFileSystemWatcherEngine: cannot create pipe");
85  kqpipe[0] = kqpipe[1] = -1;
86  return;
87  }
88  fcntl(kqpipe[0], F_SETFD, FD_CLOEXEC);
89  fcntl(kqpipe[1], F_SETFD, FD_CLOEXEC);
90 
91  struct kevent kev;
92  EV_SET(&kev,
93  kqpipe[0],
94  EVFILT_READ,
95  EV_ADD | EV_ENABLE,
96  0,
97  0,
98  0);
99  if (kevent(kqfd, &kev, 1, 0, 0, 0) == -1) {
100  perror("QKqueueFileSystemWatcherEngine: cannot watch pipe, kevent returned");
101  return;
102  }
103 }
104 
106 {
107  stop();
108  wait();
109 
110  close(kqfd);
111  close(kqpipe[0]);
112  close(kqpipe[1]);
113 
114  foreach (int id, pathToID)
115  ::close(id < 0 ? -id : id);
116 }
117 
119  QStringList *files,
120  QStringList *directories)
121 {
122  QStringList p = paths;
123  {
124  QMutexLocker locker(&mutex);
125 
126  QMutableListIterator<QString> it(p);
127  while (it.hasNext()) {
128  QString path = it.next();
129  int fd;
130 #if defined(O_EVTONLY)
131  fd = qt_safe_open(QFile::encodeName(path), O_EVTONLY);
132 #else
134 #endif
135  if (fd == -1) {
136  perror("QKqueueFileSystemWatcherEngine::addPaths: open");
137  continue;
138  }
139  if (fd >= (int)FD_SETSIZE / 2 && fd < (int)FD_SETSIZE) {
140  int fddup = fcntl(fd, F_DUPFD, FD_SETSIZE);
141  if (fddup != -1) {
142  ::close(fd);
143  fd = fddup;
144  }
145  }
146  fcntl(fd, F_SETFD, FD_CLOEXEC);
147 
148  QT_STATBUF st;
149  if (QT_FSTAT(fd, &st) == -1) {
150  perror("QKqueueFileSystemWatcherEngine::addPaths: fstat");
151  ::close(fd);
152  continue;
153  }
154  int id = (S_ISDIR(st.st_mode)) ? -fd : fd;
155  if (id < 0) {
156  if (directories->contains(path)) {
157  ::close(fd);
158  continue;
159  }
160  } else {
161  if (files->contains(path)) {
162  ::close(fd);
163  continue;
164  }
165  }
166 
167  struct kevent kev;
168  EV_SET(&kev,
169  fd,
170  EVFILT_VNODE,
171  EV_ADD | EV_ENABLE | EV_CLEAR,
172  NOTE_DELETE | NOTE_WRITE | NOTE_EXTEND | NOTE_ATTRIB | NOTE_RENAME | NOTE_REVOKE,
173  0,
174  0);
175  if (kevent(kqfd, &kev, 1, 0, 0, 0) == -1) {
176  perror("QKqueueFileSystemWatcherEngine::addPaths: kevent");
177  ::close(fd);
178  continue;
179  }
180 
181  it.remove();
182  if (id < 0) {
183  DEBUG() << "QKqueueFileSystemWatcherEngine: added directory path" << path;
184  directories->append(path);
185  } else {
186  DEBUG() << "QKqueueFileSystemWatcherEngine: added file path" << path;
187  files->append(path);
188  }
189 
190  pathToID.insert(path, id);
191  idToPath.insert(id, path);
192  }
193  }
194 
195  if (!isRunning())
196  start();
197  else
198  write(kqpipe[1], "@", 1);
199 
200  return p;
201 }
202 
204  QStringList *files,
205  QStringList *directories)
206 {
207  bool isEmpty;
208  QStringList p = paths;
209  {
210  QMutexLocker locker(&mutex);
211  if (pathToID.isEmpty())
212  return p;
213 
214  QMutableListIterator<QString> it(p);
215  while (it.hasNext()) {
216  QString path = it.next();
217  int id = pathToID.take(path);
218  QString x = idToPath.take(id);
219  if (x.isEmpty() || x != path)
220  continue;
221 
222  ::close(id < 0 ? -id : id);
223 
224  it.remove();
225  if (id < 0)
226  directories->removeAll(path);
227  else
228  files->removeAll(path);
229  }
230  isEmpty = pathToID.isEmpty();
231  }
232 
233  if (isEmpty) {
234  stop();
235  wait();
236  } else {
237  write(kqpipe[1], "@", 1);
238  }
239 
240  return p;
241 }
242 
244 {
245  write(kqpipe[1], "q", 1);
246 }
247 
249 {
250  forever {
251  int r;
252  struct kevent kev;
253  DEBUG() << "QKqueueFileSystemWatcherEngine: waiting for kevents...";
254  EINTR_LOOP(r, kevent(kqfd, 0, 0, &kev, 1, 0));
255  if (r < 0) {
256  perror("QKqueueFileSystemWatcherEngine: error during kevent wait");
257  return;
258  } else {
259  int fd = kev.ident;
260 
261  DEBUG() << "QKqueueFileSystemWatcherEngine: processing kevent" << kev.ident << kev.filter;
262  if (fd == kqpipe[0]) {
263  // read all pending data from the pipe
264  QByteArray ba;
265  ba.resize(kev.data);
266  if (read(kqpipe[0], ba.data(), ba.size()) != ba.size()) {
267  perror("QKqueueFileSystemWatcherEngine: error reading from pipe");
268  return;
269  }
270  // read the command from the buffer (but break and return on 'q')
271  char cmd = 0;
272  for (int i = 0; i < ba.size(); ++i) {
273  cmd = ba.constData()[i];
274  if (cmd == 'q')
275  break;
276  }
277  // handle the command
278  switch (cmd) {
279  case 'q':
280  DEBUG() << "QKqueueFileSystemWatcherEngine: thread received 'q', exiting...";
281  return;
282  case '@':
283  DEBUG() << "QKqueueFileSystemWatcherEngine: thread received '@', continuing...";
284  break;
285  default:
286  DEBUG() << "QKqueueFileSystemWatcherEngine: thread received unknow message" << cmd;
287  break;
288  }
289  } else {
290  QMutexLocker locker(&mutex);
291 
292  int id = fd;
293  QString path = idToPath.value(id);
294  if (path.isEmpty()) {
295  // perhaps a directory?
296  id = -id;
297  path = idToPath.value(id);
298  if (path.isEmpty()) {
299  DEBUG() << "QKqueueFileSystemWatcherEngine: received a kevent for a file we're not watching";
300  continue;
301  }
302  }
303  if (kev.filter != EVFILT_VNODE) {
304  DEBUG() << "QKqueueFileSystemWatcherEngine: received a kevent with the wrong filter";
305  continue;
306  }
307 
308  if ((kev.fflags & (NOTE_DELETE | NOTE_REVOKE | NOTE_RENAME)) != 0) {
309  DEBUG() << path << "removed, removing watch also";
310 
311  pathToID.remove(path);
312  idToPath.remove(id);
313  ::close(fd);
314 
315  if (id < 0)
316  emit directoryChanged(path, true);
317  else
318  emit fileChanged(path, true);
319  } else {
320  DEBUG() << path << "changed";
321 
322  if (id < 0)
323  emit directoryChanged(path, false);
324  else
325  emit fileChanged(path, false);
326  }
327  }
328  }
329  }
330 }
331 
332 #endif //QT_NO_FILESYSTEMWATCHER
333 
#define DEBUG
void fileChanged(const QString &path, bool removed)
#define QT_END_NAMESPACE
This macro expands to.
Definition: qglobal.h:90
char * data()
Returns a pointer to the data stored in the byte array.
Definition: qbytearray.h:429
int remove(const Key &key)
Removes all the items that have the key from the hash.
Definition: qhash.h:784
#define EINTR_LOOP(var, cmd)
Definition: qcore_unix_p.h:96
#define it(className, varName)
The QByteArray class provides an array of bytes.
Definition: qbytearray.h:135
#define O_RDONLY
void run()
The starting point for the thread.
QStringList addPaths(const QStringList &paths, QStringList *files, QStringList *directories)
The QString class provides a Unicode character string.
Definition: qstring.h:83
T take(const Key &key)
Removes the item with the key from the hash and returns the value associated with it...
Definition: qhash.h:807
const T value(const Key &key) const
Returns the value associated with the key.
Definition: qhash.h:606
iterator insert(const Key &key, const T &value)
Inserts a new item with the key and a value of value.
Definition: qhash.h:753
void append(const T &t)
Inserts value at the end of the list.
Definition: qlist.h:507
#define QT_BEGIN_NAMESPACE
This macro expands to.
Definition: qglobal.h:89
static int qt_safe_open(const char *pathname, int flags, mode_t mode=0777)
Definition: qcore_unix_p.h:171
static bool isEmpty(const char *str)
bool isEmpty() const
Returns true if the string has no characters; otherwise returns false.
Definition: qstring.h:704
#define emit
Definition: qobjectdefs.h:76
The QStringList class provides a list of strings.
Definition: qstringlist.h:66
bool isEmpty() const
Returns true if the hash contains no items; otherwise returns false.
Definition: qhash.h:297
QBool contains(const QString &str, Qt::CaseSensitivity cs=Qt::CaseSensitive) const
Returns true if the list contains the string str; otherwise returns false.
Definition: qstringlist.h:172
void directoryChanged(const QString &path, bool removed)
const char * constData() const
Returns a pointer to the data stored in the byte array.
Definition: qbytearray.h:433
QStringList removePaths(const QStringList &paths, QStringList *files, QStringList *directories)
int fcntl(int, int,...)
The QMutexLocker class is a convenience class that simplifies locking and unlocking mutexes...
Definition: qmutex.h:101
#define S_ISDIR(x)
Definition: qzip.cpp:63
void start(Priority=InheritPriority)
Begins execution of the thread by calling run().
bool wait(unsigned long time=ULONG_MAX)
Blocks the thread until either of these conditions is met:
#define st(var, type, card)
void resize(int size)
Sets the size of the byte array to size bytes.
int size() const
Returns the number of bytes in this byte array.
Definition: qbytearray.h:402
static QKqueueFileSystemWatcherEngine * create()
static QByteArray encodeName(const QString &fileName)
By default, this function converts fileName to the local 8-bit encoding determined by the user&#39;s loca...
Definition: qfile.cpp:528
bool isRunning() const
Returns true if the thread is running; otherwise returns false.
Definition: qthread.cpp:494
int removeAll(const T &t)
Removes all occurrences of value in the list and returns the number of entries removed.
Definition: qlist.h:770
#define forever
This macro is provided for convenience for writing infinite loops.
Definition: qglobal.h:2452