Bincimap 2.0.16
Easy Imapping
Loading...
Searching...
No Matches
maildir-scan.cc
Go to the documentation of this file.
1
7#include <fcntl.h>
8#include <dirent.h>
9#include <sys/stat.h>
10#include <unistd.h>
11#include <errno.h>
12
13#include "iodevice.h"
14#include "iofactory.h"
15#include "maildir.h"
16
17using namespace Binc;
18using namespace ::std;
19
20Lock::Lock(const string &path)
21{
22 lock = (path == "" ? "." : path) + "/bincimap-scan-lock";
23
24 int lockfd = -1;
25 while ((lockfd = ::open(lock.c_str(),
26 O_CREAT | O_WRONLY | O_EXCL, 0666)) == -1) {
27 if (errno != EEXIST) {
28 bincWarning << "unable to lock mailbox: " << lock
29 << ", " << string(strerror(errno)) << endl;
30 return;
31 }
32
33 struct stat mystat;
34 bincWarning << "waiting for mailbox lock " << lock << "." << endl;
35 if (lstat(lock.c_str(), &mystat) == 0) {
36 if ((time(0) - mystat.st_ctime) > 300) {
37 if (unlink(lock.c_str()) == 0) continue;
38 else bincWarning << "failed to force mailbox lock: " << lock
39 << ", " << string(strerror(errno)) << endl;
40 }
41 } else {
42 if (errno != ENOENT) {
43 string err = "invalid lock " + lock + ": "
44 + strerror(errno);
45 bincWarning << err << endl;
46 return;
47 }
48 }
49
50 // sleep one second.
51 sleep(1);
52 }
53
54 close(lockfd);
55}
56
58{
59 // remove the lock
60 if (unlink(lock.c_str()) != 0)
61 bincWarning << "failed to unlock mailbox: " << lock << ", "
62 << strerror(errno) << endl;
63}
64
65//------------------------------------------------------------------------
66// scan the maildir. update flags, find messages in new/ and move them
67// to cur, setting the recent flag in memory only. check for expunged
68// messages. give newly arrived messages uids.
69//------------------------------------------------------------------------
71{
72 const string newpath = path + "/new/";
73 const string curpath = path + "/cur/";
74 const string cachepath = path + "/bincimap-cache";
75
76 // check wether or not we need to bother scanning the folder.
77 if (firstscan || forceScan) {
78 struct stat oldstat;
79 if (stat(newpath.c_str(), &oldstat) != 0) {
80 setLastError("Invalid Mailbox, " + newpath + ": "
81 + string(strerror(errno)));
82 return PermanentError;
83 }
84
85 old_new_st_mtime = oldstat.st_mtime;
86 old_new_st_ctime = oldstat.st_ctime;
87
88 if (stat(curpath.c_str(), &oldstat) != 0) {
89 setLastError("Invalid Mailbox, " + curpath + ": "
90 + string(strerror(errno)));
91 return PermanentError;
92 }
93
94 old_cur_st_mtime = oldstat.st_mtime;
95 old_cur_st_ctime = oldstat.st_ctime;
96
97 if (stat(cachepath.c_str(), &oldstat) == 0) {
98 old_bincimap_cache_st_mtime = oldstat.st_mtime;
99 old_bincimap_cache_st_ctime = oldstat.st_ctime;
100 } else {
101 old_bincimap_cache_st_mtime = 0;
102 old_bincimap_cache_st_ctime = 0;
103 }
104 } else {
105 struct stat oldcurstat;
106 struct stat oldnewstat;
107 struct stat oldbincimapcachestat;
108 if (stat(newpath.c_str(), &oldnewstat) != 0) {
109 setLastError("Invalid Mailbox, " + newpath + ": "
110 + string(strerror(errno)));
111 return PermanentError;
112 }
113
114 if (stat(curpath.c_str(), &oldcurstat) != 0) {
115 setLastError("Invalid Mailbox, " + curpath + ": "
116 + string(strerror(errno)));
117 return PermanentError;
118 }
119
120 if (stat(cachepath.c_str(), &oldbincimapcachestat) != 0) {
121 oldbincimapcachestat.st_ctime = 0;
122 oldbincimapcachestat.st_mtime = 0;
123 }
124
125 if (oldnewstat.st_mtime == old_new_st_mtime
126 && oldnewstat.st_ctime == old_new_st_ctime
127 && oldcurstat.st_mtime == old_cur_st_mtime
128 && oldcurstat.st_ctime == old_cur_st_ctime
129 && oldbincimapcachestat.st_mtime == old_bincimap_cache_st_mtime
130 && oldbincimapcachestat.st_ctime == old_bincimap_cache_st_ctime) {
131 return Success;
132 }
133
134 old_bincimap_cache_st_mtime = oldbincimapcachestat.st_mtime;
135 old_bincimap_cache_st_ctime = oldbincimapcachestat.st_ctime;
136 old_cur_st_mtime = oldcurstat.st_mtime;
137 old_cur_st_ctime = oldcurstat.st_ctime;
138 old_new_st_mtime = oldnewstat.st_mtime;
139 old_new_st_ctime = oldnewstat.st_ctime;
140 }
141
142 // lock the directory as we are scanning. this prevents race
143 // conditions with uid delegation
144 Lock lock(path);
145
146 // Read the cache file if it's there. It holds important information
147 // about the state of the depository, and serves to communicate
148 // changes to the depot across Binc IMAP instances that can not be
149 // communicated via the depot itself.
150 switch (readCache()) {
151 case NoCache:
152 case Error:
153 // An error with reading the cache files when it's not the first
154 // time we scan the depot is treated as an error.
155 if (!firstscan && !readOnly) {
156 old_cur_st_mtime = (time_t) 0;
157 old_cur_st_ctime = (time_t) 0;
158 old_new_st_mtime = (time_t) 0;
159 old_new_st_ctime = (time_t) 0;
160 return TemporaryError;
161 }
162 mailboxchanged = true;
163 break;
164 default:
165 break;
166 }
167
168 // open new/ directory
169 DIR *pdir = opendir(newpath.c_str());
170 if (pdir == 0) {
171 string reason = "failed to open \"" + newpath + "\" (";
172 reason += strerror(errno);
173 reason += ")";
174 setLastError(reason);
175
176 return PermanentError;
177 }
178
179 // scan all entries
180 struct dirent *pdirent;
181 while ((pdirent = readdir(pdir)) != 0) {
182 // "Unless you're writing messages to a maildir, the format of a
183 // unique name is none of your business. A unique name can be
184 // anything that doesn't contain a colon (or slash) and doesn't
185 // start with a dot. Do not try to extract information from unique
186 // names." - The Maildir spec from cr.yp.to
187 string filename = pdirent->d_name;
188 if (filename[0] == '.'
189 || filename.find(':') != string::npos
190 || filename.find('/') != string::npos)
191 continue;
192
193 string fullfilename = newpath + filename;
194
195 // We need to find the timestamp of the message in order to
196 // determine whether or not it's safe to move the message in from
197 // new/. qmail's default message file naming algorithm forces us
198 // to never move messages out of new/ that are less than one
199 // second old.
200 struct stat mystat;
201 if (stat(fullfilename.c_str(), &mystat) != 0) {
202 if (errno == ENOENT) {
203 // prevent looping due to stale symlinks
204 if (lstat(fullfilename.c_str(), &mystat) == 0) {
205 bincWarning << "dangling symlink: " << fullfilename << endl;
206 continue;
207 }
208
209 // a rare race between readdir and stat force us to restart the scan.
210 closedir(pdir);
211
212 if ((pdir = opendir(newpath.c_str())) == 0) {
213 string reason = "Warning: opendir(\"" + newpath + "\") == 0 (";
214 reason += strerror(errno);
215 reason += ")";
216 setLastError(reason);
217 return PermanentError;
218 }
219 } else
220 bincWarning << "junk in Maildir: \"" << fullfilename << "\": "
221 << strerror(errno);
222
223 continue;
224 }
225
226 // this is important. do not move messages from new/ that are not
227 // at least one second old or messages may disappear. this
228 // introduces a special case: we can not cache the old st_ctime
229 // and st_mtime. the next time the mailbox is scanned, it must not
230 // simply be skipped. :-)
231
232 vector<MaildirMessage>::const_iterator newIt = newMessages.begin();
233 bool ours = false;
234 for (; newIt != newMessages.end(); ++newIt) {
235 if ((filename == (*newIt).getUnique())
236 && ((*newIt).getInternalFlags() & MaildirMessage::Committed)) {
237 ours = true;
238 break;
239 }
240 }
241
242 if (!ours && ::time(0) <= mystat.st_mtime) {
243 old_cur_st_mtime = (time_t) 0;
244 old_cur_st_ctime = (time_t) 0;
245 old_new_st_mtime = (time_t) 0;
246 old_new_st_ctime = (time_t) 0;
247 continue;
248 }
249
250 // move files from new/ to cur/
251 string newName = curpath + pdirent->d_name;
252 if (rename((newpath + pdirent->d_name).c_str(),
253 (newName + ":2,").c_str()) != 0) {
254 bincWarning << "error moving messages from new to cur: skipping "
255 << newpath
256 << pdirent->d_name << ": " << strerror(errno) << endl;
257 continue;
258 }
259 }
260
261 closedir(pdir);
262
263 // Now, assume all known messages were expunged and have them prove
264 // otherwise.
265 {
268 for (; i != end(); ++i)
269 (*i).setExpunged();
270 }
271
272 // Then, scan cur
273 // open directory
274
275 if ((pdir = opendir(curpath.c_str())) == 0) {
276 string reason = "Maildir::scan::opendir(\"" + curpath + "\") == 0 (";
277 reason += strerror(errno);
278 reason += ")";
279
280 setLastError(reason);
281 return PermanentError;
282 }
283
284 // erase all old maps between fixed filenames and actual file names.
285 // we'll get a new list now, which will be more up to date.
286 index.clearFileNames();
287
288 // this is to sort recent messages by internaldate
289 multimap<unsigned int, MaildirMessage> tempMessageMap;
290
291 // scan all entries
292 while ((pdirent = readdir(pdir)) != 0) {
293 string filename = pdirent->d_name;
294 if (filename[0] == '.') continue;
295
296 string uniquename;
297 string standard;
298 string::size_type pos;
299 if ((pos = filename.find(':')) != string::npos) {
300 uniquename = filename.substr(0, pos);
301 string tmp = filename.substr(pos);
302 if ((pos = tmp.find("2,")) != string::npos)
303 standard = tmp.substr(pos + 2);
304 } else
305 uniquename = filename;
306
307 unsigned char mflags = Message::F_NONE;
308 for (string::const_iterator i = standard.begin();
309 i != standard.end(); ++i) {
310 switch (*i) {
311 case 'R': mflags |= Message::F_ANSWERED; break;
312 case 'S': mflags |= Message::F_SEEN; break;
313 case 'T': mflags |= Message::F_DELETED; break;
314 case 'D': mflags |= Message::F_DRAFT; break;
315 case 'F': mflags |= Message::F_FLAGGED; break;
316 case 'P': mflags |= Message::F_PASSED; break;
317 default: break;
318 }
319 }
320
321 struct stat mystat;
322 MaildirMessage *message = get(uniquename);
323 if (!message || message->getInternalDate() == 0) {
324 string fullfilename = curpath + filename;
325 if (stat(fullfilename.c_str(), &mystat) != 0) {
326 if (errno == ENOENT) {
327 // prevent looping due to stale symlinks
328 if (lstat(fullfilename.c_str(), &mystat) == 0) {
329 bincWarning << "dangling symlink: " << fullfilename << endl;
330 continue;
331 }
332 // a rare race between readdir and stat force us to restart
333 // the scan.
334 index.clearFileNames();
335
336 closedir(pdir);
337
338 if ((pdir = opendir(newpath.c_str())) == 0) {
339 string reason = "Warning: opendir(\"" + newpath + "\") == 0 (";
340 reason += strerror(errno);
341 reason += ")";
342 setLastError(reason);
343 return PermanentError;
344 }
345 }
346
347 continue;
348 }
349
350 mailboxchanged = true;
351 }
352
353 index.insert(uniquename, 0, filename);
354
355 // If we have this message in memory already..
356 if (message) {
357 if (message->getInternalDate() == 0) {
358 mailboxchanged = true;
359 message->setInternalDate(mystat.st_mtime);
360 }
361
362 // then confirm that this message was not expunged
363 message->setUnExpunged();
364
365 // update the flags with what new flags we found in the filename,
366 // but keep the \Recent flag regardless.
367 if (mflags != (message->getStdFlags() & ~Message::F_RECENT)) {
368 int oldflags = message->getStdFlags();
369 message->resetStdFlags();
370 message->setStdFlag(mflags | (oldflags & Message::F_RECENT));
371 }
372
373 continue;
374 }
375
376 // Wait with delegating UIDs until all entries have been
377 // read. Only then can we sort by internaldate and delegate new
378 // UIDs.
379 MaildirMessage m(*this);
380 m.setUID(0);
381 m.setSize(0);
382 m.setInternalDate(mystat.st_mtime);
384 m.setUnique(uniquename);
385 tempMessageMap.insert(make_pair((unsigned int) mystat.st_mtime, m));
386
387 mailboxchanged = true;
388 }
389
390 closedir(pdir);
391
392 // Recent messages are added, ordered by internaldate.
393 {
394 int readonlyuidnext = uidnext;
395 multimap<unsigned int, MaildirMessage>::iterator i = tempMessageMap.begin();
396 while (i != tempMessageMap.end()) {
397 i->second.setUID(readOnly ? readonlyuidnext++ : uidnext++);
398 multimap<unsigned int, MaildirMessage>::iterator itmp = i;
399 ++itmp;
400 add(i->second);
401 tempMessageMap.erase(i);
402 i = itmp;
403 mailboxchanged = true;
404 }
405 }
406
407 tempMessageMap.clear();
408
409 // Messages that existed in the cache that we read, but did not
410 // exist in the Maildir, are removed from the messages list.
412 while (jj != end()) {
413 MaildirMessage &message = (MaildirMessage &)*jj;
414
415 if (message.isExpunged()) {
416 mailboxchanged = true;
418 jj.erase();
419 continue;
420 }
421 } else if (message.getInternalFlags() & MaildirMessage::JustArrived) {
423 }
424
425 ++jj;
426 }
427
428 // Special case: The first time we scan is in SELECT. All flags
429 // changes for new messages will then appear to be recent, and
430 // to avoid this to be sent to the client as a pending update,
431 // we explicitly unset the "flagsChanged" flag in all messages.
432 if (firstscan) {
433 unsigned int lastuid = 0;
434
437 for (; ii != end(); ++ii) {
438 MaildirMessage &message = (MaildirMessage &)*ii;
440
441 if (lastuid < message.getUID())
442 lastuid = message.getUID();
443 else {
444 bincWarning << "UID values are not strictly ascending in this"
445 " mailbox: " << path << ". This is usually caused by "
446 << "access from a broken accessor. Bumping UIDVALIDITY."
447 << endl;
448
449 setLastError("An error occurred while scanning the mailbox. "
450 "Please contact your system administrator.");
451
452 if (!readOnly) {
453 bumpUidValidity(path);
454 old_cur_st_mtime = (time_t) 0;
455 old_cur_st_ctime = (time_t) 0;
456 old_new_st_mtime = (time_t) 0;
457 old_new_st_ctime = (time_t) 0;
458 return TemporaryError;
459 } else {
460 return PermanentError;
461 }
462 }
463
464 message.setFlagsUnchanged();
465 }
466 }
467
468 if (mailboxchanged && !readOnly) {
469 if (!writeCache()) return PermanentError;
470 mailboxchanged = false;
471 }
472
473 firstscan = false;
474 newMessages.clear();
475 return Success;
476}
Lock(const std::string &path)
Definition: maildir-scan.cc:20
void setLastError(const std::string &error) const
Definition: mailbox.cc:109
bool readOnly
Definition: mailbox.h:125
@ INCLUDE_EXPUNGED
Definition: mailbox.h:68
void bumpUidValidity(const std::string &) const
Definition: maildir.cc:282
void add(MaildirMessage &m)
Definition: maildir.cc:770
Mailbox::iterator end(void) const
Definition: maildir.cc:156
bool writeCache(void)
ScanResult scan(bool forceScan=false)
Definition: maildir-scan.cc:70
ReadCacheResult readCache(void)
Mailbox::iterator begin(const SequenceSet &bset, unsigned int mod=INCLUDE_EXPUNGED|SQNR_MODE) const
Definition: maildir.cc:146
MaildirMessage * get(const std::string &id)
Definition: maildir.cc:756
@ TemporaryError
Definition: maildir.h:151
@ PermanentError
Definition: maildir.h:152
void clearFileNames(void)
Definition: maildir.cc:834
void insert(const std::string &unique, unsigned int uid, const std::string &fileName="")
Definition: maildir.cc:786
The MaildirMessage class provides an interface for IMAP messages.
void setUnique(const std::string &s_in)
void setInternalDate(time_t internaldate)
unsigned char getInternalFlags(void) const
void setFlagsUnchanged(void)
void setUID(unsigned int uid)
unsigned int getUID(void) const
void setSize(unsigned int size)
bool isExpunged(void) const
void clearInternalFlag(unsigned char flags)
void setStdFlag(unsigned char flags)
static SequenceSet & all(void)
Definition: imapparser.cc:261
Declaration of the IODevice class.
Declaration of the IOFactory class.
#define bincWarning
Definition: iofactory.h:44
Declaration of the Maildir class.
Definition: bincimapd.cc:9