PageRenderTime 28ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/dovecot-2.1.8/src/lib-index/mail-index-fsck.c

#
C | 481 lines | 382 code | 69 blank | 30 comment | 88 complexity | 8f5218bdc3b79d0068033062d5d8f92f MD5 | raw file
Possible License(s): BSD-3-Clause, LGPL-2.1
  1. /* Copyright (c) 2004-2012 Dovecot authors, see the included COPYING file */
  2. #include "lib.h"
  3. #include "ioloop.h"
  4. #include "array.h"
  5. #include "mail-index-private.h"
  6. #include "mail-transaction-log-private.h"
  7. static void mail_index_fsck_error(struct mail_index *index,
  8. const char *fmt, ...) ATTR_FORMAT(2, 3);
  9. static void mail_index_fsck_error(struct mail_index *index,
  10. const char *fmt, ...)
  11. {
  12. va_list va;
  13. va_start(va, fmt);
  14. mail_index_set_error(index, "Fixed index file %s: %s",
  15. index->filepath, t_strdup_vprintf(fmt, va));
  16. va_end(va);
  17. }
  18. #define CHECK(field, oper) \
  19. if (hdr->field oper map->hdr.field) { \
  20. mail_index_fsck_error(index, #field" %u -> %u", \
  21. map->hdr.field, hdr->field); \
  22. }
  23. static void
  24. mail_index_fsck_log_pos(struct mail_index *index, struct mail_index_map *map,
  25. struct mail_index_header *hdr)
  26. {
  27. uint32_t file_seq;
  28. uoff_t file_offset;
  29. mail_transaction_log_get_head(index->log, &file_seq, &file_offset);
  30. if (hdr->log_file_seq < file_seq) {
  31. hdr->log_file_head_offset = hdr->log_file_tail_offset =
  32. sizeof(struct mail_transaction_log_header);
  33. } else {
  34. if (hdr->log_file_head_offset > file_offset)
  35. hdr->log_file_head_offset = file_offset;
  36. if (hdr->log_file_tail_offset > hdr->log_file_head_offset)
  37. hdr->log_file_tail_offset = hdr->log_file_head_offset;
  38. }
  39. hdr->log_file_seq = file_seq;
  40. CHECK(log_file_seq, !=);
  41. if (hdr->log_file_seq == map->hdr.log_file_seq) {
  42. /* don't bother complaining about these if file changed too */
  43. CHECK(log_file_head_offset, !=);
  44. CHECK(log_file_tail_offset, !=);
  45. }
  46. }
  47. static void
  48. mail_index_fsck_header(struct mail_index *index, struct mail_index_map *map,
  49. struct mail_index_header *hdr)
  50. {
  51. /* mail_index_map_check_header() has already checked that the index
  52. isn't completely broken. */
  53. if (hdr->uid_validity == 0 && hdr->next_uid != 1)
  54. hdr->uid_validity = ioloop_time;
  55. if (index->log->head != NULL)
  56. mail_index_fsck_log_pos(index, map, hdr);
  57. }
  58. static bool
  59. array_has_name(const ARRAY_TYPE(const_string) *names, const char *name)
  60. {
  61. const char *const *namep;
  62. array_foreach(names, namep) {
  63. if (strcmp(*namep, name) == 0)
  64. return TRUE;
  65. }
  66. return FALSE;
  67. }
  68. static unsigned int
  69. mail_index_fsck_find_keyword_count(struct mail_index_map *map,
  70. const struct mail_index_ext_header *ext_hdr)
  71. {
  72. const struct mail_index_record *rec;
  73. const uint8_t *kw;
  74. unsigned int r, i, j, cur, max = 0, kw_pos, kw_size;
  75. kw_pos = ext_hdr->record_offset;
  76. kw_size = ext_hdr->record_size;
  77. rec = map->rec_map->records;
  78. for (r = 0; r < map->rec_map->records_count; r++) {
  79. kw = CONST_PTR_OFFSET(rec, kw_pos);
  80. for (i = cur = 0; i < kw_size; i++) {
  81. if (kw[i] != 0) {
  82. for (j = 0; j < 8; j++) {
  83. if ((kw[i] & (1 << j)) != 0)
  84. cur = i * 8 + j + 1;
  85. }
  86. }
  87. }
  88. if (cur > max) {
  89. max = cur;
  90. if (max == kw_size*8)
  91. return max;
  92. }
  93. rec = CONST_PTR_OFFSET(rec, map->hdr.record_size);
  94. }
  95. return max;
  96. }
  97. static bool
  98. keyword_name_is_valid(const char *buffer, unsigned int pos, unsigned int size)
  99. {
  100. for (; pos < size; pos++) {
  101. if (buffer[pos] == '\0')
  102. return TRUE;
  103. if (((unsigned char)buffer[pos] & 0x7f) < 32) {
  104. /* control characters aren't valid */
  105. return FALSE;
  106. }
  107. }
  108. return FALSE;
  109. }
  110. static void
  111. mail_index_fsck_keywords(struct mail_index *index, struct mail_index_map *map,
  112. struct mail_index_header *hdr,
  113. const struct mail_index_ext_header *ext_hdr,
  114. unsigned int ext_offset, unsigned int *offset_p)
  115. {
  116. const struct mail_index_keyword_header *kw_hdr;
  117. struct mail_index_keyword_header *new_kw_hdr;
  118. const struct mail_index_keyword_header_rec *kw_rec;
  119. struct mail_index_keyword_header_rec new_kw_rec;
  120. const char *name, *name_buffer, **name_array;
  121. unsigned int i, j, name_pos, name_size, rec_pos, hdr_offset, diff;
  122. unsigned int changed_count, keywords_count, name_base_pos;
  123. ARRAY_TYPE(const_string) names;
  124. buffer_t *dest;
  125. bool changed = FALSE;
  126. hdr_offset = ext_offset +
  127. mail_index_map_ext_hdr_offset(sizeof(MAIL_INDEX_EXT_KEYWORDS)-1);
  128. kw_hdr = CONST_PTR_OFFSET(map->hdr_base, hdr_offset);
  129. keywords_count = kw_hdr->keywords_count;
  130. kw_rec = (const void *)(kw_hdr + 1);
  131. name_buffer = (const char *)(kw_rec + keywords_count);
  132. name_pos = (size_t)(name_buffer - (const char *)kw_hdr);
  133. if (name_pos > ext_hdr->hdr_size) {
  134. /* the header is completely broken */
  135. keywords_count =
  136. mail_index_fsck_find_keyword_count(map, ext_hdr);
  137. mail_index_fsck_error(index, "Assuming keywords_count = %u",
  138. keywords_count);
  139. kw_rec = NULL;
  140. name_size = 0;
  141. changed = TRUE;
  142. } else {
  143. name_size = ext_hdr->hdr_size - name_pos;
  144. }
  145. /* create keyword name array. invalid keywords are added as
  146. empty strings */
  147. t_array_init(&names, keywords_count);
  148. for (i = 0; i < keywords_count; i++) {
  149. if (name_size == 0 ||
  150. !keyword_name_is_valid(name_buffer, kw_rec[i].name_offset,
  151. name_size))
  152. name = "";
  153. else
  154. name = name_buffer + kw_rec[i].name_offset;
  155. if (*name != '\0' && array_has_name(&names, name)) {
  156. /* duplicate */
  157. name = "";
  158. }
  159. array_append(&names, &name, 1);
  160. }
  161. /* give new names to invalid keywords */
  162. changed_count = 0;
  163. name_array = array_idx_modifiable(&names, 0);
  164. for (i = j = 0; i < keywords_count; i++) {
  165. while (name_array[i][0] == '\0') {
  166. name = t_strdup_printf("unknown-%d", j++);
  167. if (!array_has_name(&names, name)) {
  168. name_array[i] = name;
  169. changed = TRUE;
  170. changed_count++;
  171. }
  172. }
  173. }
  174. if (!changed) {
  175. /* nothing was broken */
  176. return;
  177. }
  178. mail_index_fsck_error(index, "Renamed %u keywords to unknown-*",
  179. changed_count);
  180. dest = buffer_create_dynamic(default_pool,
  181. I_MAX(ext_hdr->hdr_size, 128));
  182. new_kw_hdr = buffer_append_space_unsafe(dest, sizeof(*new_kw_hdr));
  183. new_kw_hdr->keywords_count = keywords_count;
  184. /* add keyword records so we can start appending names directly */
  185. rec_pos = dest->used;
  186. memset(&new_kw_rec, 0, sizeof(new_kw_rec));
  187. buffer_append_space_unsafe(dest, keywords_count * sizeof(*kw_rec));
  188. /* write the actual records and names */
  189. name_base_pos = dest->used;
  190. for (i = 0; i < keywords_count; i++) {
  191. new_kw_rec.name_offset = dest->used - name_base_pos;
  192. buffer_write(dest, rec_pos, &new_kw_rec, sizeof(new_kw_rec));
  193. rec_pos += sizeof(*kw_rec);
  194. buffer_append(dest, name_array[i], strlen(name_array[i]) + 1);
  195. }
  196. /* keep the header size at least the same size as before */
  197. if (dest->used < ext_hdr->hdr_size)
  198. buffer_append_zero(dest, ext_hdr->hdr_size - dest->used);
  199. if (dest->used > ext_hdr->hdr_size) {
  200. /* need to resize the header */
  201. struct mail_index_ext_header new_ext_hdr;
  202. diff = dest->used - ext_hdr->hdr_size;
  203. buffer_copy(map->hdr_copy_buf, hdr_offset + diff,
  204. map->hdr_copy_buf, hdr_offset, (size_t)-1);
  205. map->hdr_base = map->hdr_copy_buf->data;
  206. hdr->header_size += diff;
  207. *offset_p += diff;
  208. new_ext_hdr = *ext_hdr;
  209. new_ext_hdr.hdr_size += diff;
  210. buffer_write(map->hdr_copy_buf, ext_offset,
  211. &new_ext_hdr, sizeof(new_ext_hdr));
  212. }
  213. i_assert(hdr_offset + dest->used <= map->hdr_copy_buf->used);
  214. buffer_write(map->hdr_copy_buf, hdr_offset, dest->data, dest->used);
  215. /* keywords changed unexpectedly, so all views are broken now */
  216. index->inconsistency_id++;
  217. buffer_free(&dest);
  218. }
  219. static void
  220. mail_index_fsck_extensions(struct mail_index *index, struct mail_index_map *map,
  221. struct mail_index_header *hdr)
  222. {
  223. const struct mail_index_ext_header *ext_hdr;
  224. ARRAY_TYPE(const_string) names;
  225. const char *name, *error;
  226. unsigned int offset, next_offset, i;
  227. t_array_init(&names, 64);
  228. offset = MAIL_INDEX_HEADER_SIZE_ALIGN(hdr->base_header_size);
  229. for (i = 0; offset < hdr->header_size; i++) {
  230. /* mail_index_map_ext_get_next() uses map->hdr, so make sure
  231. it's up-to-date */
  232. map->hdr = *hdr;
  233. next_offset = offset;
  234. if (mail_index_map_ext_get_next(map, &next_offset,
  235. &ext_hdr, &name) < 0) {
  236. /* the extension continued outside header, drop it */
  237. mail_index_fsck_error(index,
  238. "Dropped extension #%d (%s) "
  239. "with invalid header size",
  240. i, name);
  241. hdr->header_size = offset;
  242. break;
  243. }
  244. if (mail_index_map_ext_hdr_check(hdr, ext_hdr, name,
  245. &error) < 0) {
  246. mail_index_fsck_error(index,
  247. "Dropped broken extension #%d (%s)", i, name);
  248. } else if (array_has_name(&names, name)) {
  249. mail_index_fsck_error(index,
  250. "Dropped duplicate extension %s", name);
  251. } else {
  252. /* name may change if header buffer is changed */
  253. name = t_strdup(name);
  254. if (strcmp(name, MAIL_INDEX_EXT_KEYWORDS) == 0) {
  255. mail_index_fsck_keywords(index, map, hdr,
  256. ext_hdr, offset,
  257. &next_offset);
  258. }
  259. array_append(&names, &name, 1);
  260. offset = next_offset;
  261. continue;
  262. }
  263. /* drop the field */
  264. hdr->header_size -= next_offset - offset;
  265. buffer_copy(map->hdr_copy_buf, offset,
  266. map->hdr_copy_buf, next_offset, (size_t)-1);
  267. buffer_set_used_size(map->hdr_copy_buf, hdr->header_size);
  268. map->hdr_base = map->hdr_copy_buf->data;
  269. }
  270. }
  271. static void
  272. mail_index_fsck_records(struct mail_index *index, struct mail_index_map *map,
  273. struct mail_index_header *hdr)
  274. {
  275. struct mail_index_record *rec, *next_rec;
  276. uint32_t i, last_uid;
  277. bool logged_unordered_uids = FALSE, logged_zero_uids = FALSE;
  278. bool records_dropped = FALSE;
  279. hdr->messages_count = 0;
  280. hdr->seen_messages_count = 0;
  281. hdr->deleted_messages_count = 0;
  282. hdr->first_unseen_uid_lowwater = 0;
  283. hdr->first_deleted_uid_lowwater = 0;
  284. rec = map->rec_map->records; last_uid = 0;
  285. for (i = 0; i < map->rec_map->records_count; ) {
  286. next_rec = PTR_OFFSET(rec, hdr->record_size);
  287. if (rec->uid <= last_uid) {
  288. /* log an error once, and skip this record */
  289. if (rec->uid == 0) {
  290. if (!logged_zero_uids) {
  291. mail_index_fsck_error(index,
  292. "Record UIDs have zeroes");
  293. logged_zero_uids = TRUE;
  294. }
  295. } else {
  296. if (!logged_unordered_uids) {
  297. mail_index_fsck_error(index,
  298. "Record UIDs unordered");
  299. logged_unordered_uids = TRUE;
  300. }
  301. }
  302. /* not the fastest way when we're skipping lots of
  303. records, but this should happen rarely so don't
  304. bother optimizing. */
  305. memmove(rec, next_rec, hdr->record_size *
  306. (map->rec_map->records_count - i - 1));
  307. map->rec_map->records_count--;
  308. records_dropped = TRUE;
  309. continue;
  310. }
  311. hdr->messages_count++;
  312. if ((rec->flags & MAIL_SEEN) != 0)
  313. hdr->seen_messages_count++;
  314. if ((rec->flags & MAIL_DELETED) != 0)
  315. hdr->deleted_messages_count++;
  316. if ((rec->flags & MAIL_SEEN) == 0 &&
  317. hdr->first_unseen_uid_lowwater == 0)
  318. hdr->first_unseen_uid_lowwater = rec->uid;
  319. if ((rec->flags & MAIL_DELETED) != 0 &&
  320. hdr->first_deleted_uid_lowwater == 0)
  321. hdr->first_deleted_uid_lowwater = rec->uid;
  322. last_uid = rec->uid;
  323. rec = next_rec;
  324. i++;
  325. }
  326. if (records_dropped) {
  327. /* all existing views are broken now */
  328. index->inconsistency_id++;
  329. }
  330. if (hdr->next_uid <= last_uid) {
  331. mail_index_fsck_error(index, "next_uid %u -> %u",
  332. hdr->next_uid, last_uid+1);
  333. hdr->next_uid = last_uid+1;
  334. }
  335. if (hdr->first_unseen_uid_lowwater == 0)
  336. hdr->first_unseen_uid_lowwater = hdr->next_uid;
  337. if (hdr->first_deleted_uid_lowwater == 0)
  338. hdr->first_deleted_uid_lowwater = hdr->next_uid;
  339. if (hdr->first_recent_uid > hdr->next_uid)
  340. hdr->first_recent_uid = hdr->next_uid;
  341. if (hdr->first_recent_uid == 0)
  342. hdr->first_recent_uid = 1;
  343. CHECK(uid_validity, !=);
  344. CHECK(messages_count, !=);
  345. CHECK(seen_messages_count, !=);
  346. CHECK(deleted_messages_count, !=);
  347. CHECK(first_unseen_uid_lowwater, <);
  348. CHECK(first_deleted_uid_lowwater, <);
  349. CHECK(first_recent_uid, !=);
  350. }
  351. static void
  352. mail_index_fsck_map(struct mail_index *index, struct mail_index_map *map)
  353. {
  354. struct mail_index_header hdr;
  355. if (index->log->head != NULL) {
  356. /* Remember the log head position. If we go back in the index's
  357. head offset, ignore errors in the log up to this offset. */
  358. mail_transaction_log_get_head(index->log,
  359. &index->fsck_log_head_file_seq,
  360. &index->fsck_log_head_file_offset);
  361. }
  362. hdr = map->hdr;
  363. mail_index_fsck_header(index, map, &hdr);
  364. mail_index_fsck_extensions(index, map, &hdr);
  365. mail_index_fsck_records(index, map, &hdr);
  366. map->hdr = hdr;
  367. }
  368. int mail_index_fsck(struct mail_index *index)
  369. {
  370. bool orig_locked = index->log_sync_locked;
  371. struct mail_index_map *map;
  372. uint32_t file_seq;
  373. uoff_t file_offset;
  374. i_warning("fscking index file %s", index->filepath);
  375. index->fscked = TRUE;
  376. if (index->log->head == NULL) {
  377. /* we're trying to open the index files, but there wasn't
  378. any .log file. */
  379. if (mail_transaction_log_create(index->log, FALSE) < 0)
  380. return -1;
  381. }
  382. if (!orig_locked) {
  383. if (mail_transaction_log_sync_lock(index->log, &file_seq,
  384. &file_offset) < 0)
  385. return -1;
  386. }
  387. map = mail_index_map_clone(index->map);
  388. mail_index_unmap(&index->map);
  389. index->map = map;
  390. T_BEGIN {
  391. mail_index_fsck_map(index, map);
  392. } T_END;
  393. map->write_base_header = TRUE;
  394. map->write_atomic = TRUE;
  395. mail_index_write(index, FALSE);
  396. if (!orig_locked)
  397. mail_transaction_log_sync_unlock(index->log);
  398. return 0;
  399. }
  400. void mail_index_fsck_locked(struct mail_index *index)
  401. {
  402. int ret;
  403. i_assert(index->log_sync_locked);
  404. ret = mail_index_fsck(index);
  405. i_assert(ret == 0);
  406. }
  407. bool mail_index_reset_fscked(struct mail_index *index)
  408. {
  409. bool ret = index->fscked;
  410. index->fscked = FALSE;
  411. return ret;
  412. }