static int maildir_fix_duplicate(struct maildir_sync_context *ctx, const char *dir, const char *fname2) { const char *fname1, *path1, *path2; const char *new_fname, *new_path; struct stat st1, st2; fname1 = maildir_uidlist_sync_get_full_filename(ctx->uidlist_sync_ctx, fname2); i_assert(fname1 != NULL); path1 = t_strconcat(dir, "/", fname1, NULL); path2 = t_strconcat(dir, "/", fname2, NULL); if (stat(path1, &st1) < 0 || stat(path2, &st2) < 0) { /* most likely the files just don't exist anymore. don't really care about other errors much. */ return 0; } if (st1.st_ino == st2.st_ino && CMP_DEV_T(st1.st_dev, st2.st_dev)) { /* Files are the same. this means either a race condition between stat() calls, or that the files were link()ed. */ if (st1.st_nlink > 1 && st2.st_nlink == st1.st_nlink && st1.st_ctime == st2.st_ctime && st1.st_ctime < ioloop_time - DUPE_LINKS_DELETE_SECS) { /* The file has hard links and it hasn't had any changes (such as renames) for a while, so this isn't a race condition. rename()ing one file on top of the other would fix this safely, except POSIX decided that rename() doesn't work that way. So we'll have unlink() one and hope that another process didn't just decide to unlink() the other (uidlist lock prevents this from happening) */ if (unlink(path2) == 0) i_warning("Unlinked a duplicate: %s", path2); else { mail_storage_set_critical( &ctx->mbox->storage->storage, "unlink(%s) failed: %m", path2); } } return 0; } new_fname = maildir_filename_generate(); new_path = t_strconcat(ctx->mbox->box.path, "/new/", new_fname, NULL); if (rename(path2, new_path) == 0) i_warning("Fixed a duplicate: %s -> %s", path2, new_fname); else if (errno != ENOENT) { mail_storage_set_critical(&ctx->mbox->storage->storage, "Couldn't fix a duplicate: rename(%s, %s) failed: %m", path2, new_path); return -1; } return 0; }
static int maildir_create_tmp(struct maildir_mailbox *mbox, const char *dir, const char **fname_r) { struct mailbox *box = &mbox->box; const struct mailbox_permissions *perm = mailbox_get_permissions(box); unsigned int prefix_len; const char *tmp_fname; string_t *path; mode_t old_mask; int fd; path = t_str_new(256); str_append(path, dir); str_append_c(path, '/'); prefix_len = str_len(path); do { tmp_fname = maildir_filename_generate(); str_truncate(path, prefix_len); str_append(path, tmp_fname); /* the generated filename is unique. the only reason why it might return an existing filename is if the time moved backwards. so we'll use O_EXCL anyway, although it's mostly useless. */ old_mask = umask(0777 & ~perm->file_create_mode); fd = open(str_c(path), O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, 0777); umask(old_mask); } while (fd == -1 && errno == EEXIST); *fname_r = tmp_fname; if (fd == -1) { if (ENOQUOTA(errno)) { mail_storage_set_error(box->storage, MAIL_ERROR_NOQUOTA, MAIL_ERRSTR_NO_QUOTA); } else { mail_storage_set_critical(box->storage, "open(%s) failed: %m", str_c(path)); } } else if (perm->file_create_gid != (gid_t)-1) { if (fchown(fd, (uid_t)-1, perm->file_create_gid) < 0) { if (errno == EPERM) { mail_storage_set_critical(box->storage, "%s", eperm_error_get_chgrp("fchown", str_c(path), perm->file_create_gid, perm->file_create_gid_origin)); } else { mail_storage_set_critical(box->storage, "fchown(%s) failed: %m", str_c(path)); } } } return fd; }
static int maildir_rename_empty_basename(struct maildir_sync_context *ctx, const char *dir, const char *fname) { const char *old_path, *new_fname, *new_path; old_path = t_strconcat(dir, "/", fname, NULL); new_fname = maildir_filename_generate(); new_path = t_strconcat(mailbox_get_path(&ctx->mbox->box), "/new/", new_fname, NULL); if (rename(old_path, new_path) == 0) i_warning("Fixed broken filename: %s -> %s", old_path, new_fname); else if (errno != ENOENT) { mail_storage_set_critical(&ctx->mbox->storage->storage, "Couldn't fix a broken filename: rename(%s, %s) failed: %m", old_path, new_path); return -1; } return 0; }
static int maildir_copy_hardlink(struct mail_save_context *ctx, struct mail *mail) { struct maildir_mailbox *dest_mbox = (struct maildir_mailbox *)ctx->transaction->box; struct maildir_mailbox *src_mbox; struct maildir_filename *mf; struct hardlink_ctx do_ctx; const char *path, *guid, *dest_fname; uoff_t vsize, size; enum mail_lookup_abort old_abort; if (strcmp(mail->box->storage->name, MAILDIR_STORAGE_NAME) == 0) src_mbox = (struct maildir_mailbox *)mail->box; else if (strcmp(mail->box->storage->name, "raw") == 0) { /* lda uses raw format */ src_mbox = NULL; } else { /* Can't hard link files from the source storage */ return 0; } /* hard link to tmp/ with a newly generated filename and later when we have uidlist locked, move it to new/cur. */ dest_fname = maildir_filename_generate(); memset(&do_ctx, 0, sizeof(do_ctx)); do_ctx.dest_path = t_strdup_printf("%s/tmp/%s", mailbox_get_path(&dest_mbox->box), dest_fname); if (src_mbox != NULL) { /* maildir */ if (maildir_file_do(src_mbox, mail->uid, do_hardlink, &do_ctx) < 0) return -1; } else { /* raw / lda */ if (mail_get_special(mail, MAIL_FETCH_UIDL_FILE_NAME, &path) < 0 || *path == '\0') return 0; if (do_hardlink(dest_mbox, path, &do_ctx) < 0) return -1; } if (!do_ctx.success) { /* couldn't copy with hardlinking, fallback to copying */ return 0; } /* hardlinked to tmp/, treat as normal copied mail */ mf = maildir_save_add(ctx, dest_fname, mail); if (mail_get_special(mail, MAIL_FETCH_GUID, &guid) == 0) { if (*guid != '\0') maildir_save_set_dest_basename(ctx, mf, guid); } /* remember size/vsize if possible */ old_abort = mail->lookup_abort; mail->lookup_abort = MAIL_LOOKUP_ABORT_READ_MAIL; if (mail_get_physical_size(mail, &size) < 0) size = (uoff_t)-1; if (mail_get_virtual_size(mail, &vsize) < 0) vsize = (uoff_t)-1; maildir_save_set_sizes(mf, size, vsize); mail->lookup_abort = old_abort; return 1; }