Windows single-file mailbox rewrite can remove the mailbox when the fallback rename fails

未關閉
#156,699 0 則留言 0 個 reaction 已指派 0 人 在 GitHub 檢視

還沒有人認領這個 Issue。

評估

難度
3/5
預估耗時
1-2 天
新手友好度
35/100
Issue 類型
缺陷
描述清晰度
描述清楚
活躍度
停滯
技術堆疊
python
領域
backend

研究方向

從 mailbox 模組中的 _singlefileMailbox.flush() 開始,檢查 mailbox.mbox、mailbox.MMDF 和 mailbox.Babyl 的替換路徑。重現 issue 中提到的兩個模擬 os.rename 失敗,然後新增或更新回歸測試,以確保替換失敗時原始 mailbox 路徑名稱仍然保留;開始前先查看關聯的 PR gh-156700。

由索引模型根據 Issue 內容生成。

描述

OS-windows stdlib topic-email type-bug

Bug report

Bug description

Summary

On Windows, _singlefileMailbox.flush() cannot rename a temporary rewrite over an existing mailbox with os.rename(). Its FileExistsError fallback deletes the original mailbox and then performs a second rename. If that second filesystem operation fails, the mailbox pathname has already been removed.

The affected mailbox classes are mailbox.mbox, mailbox.MMDF, and mailbox.Babyl. The temporary replacement normally remains, so the contents are not necessarily irretrievable, but the original mailbox pathname is missing. This is a Windows-specific failure path because it depends on Windows os.rename() refusing to replace an existing destination.

Reproduction Code
import errno
import glob
import mailbox
import os
import tempfile
from unittest.mock import patch

with tempfile.TemporaryDirectory() as directory:
    path = os.path.join(directory, "mailbox")
    box = mailbox.mbox(path)
    first = box.add(b"Subject: first\n\nfirst\n")
    box.add(b"Subject: second\n\nsecond\n")
    box.flush()
    box.remove(first)

    # Simulate Windows rejecting the first replacement attempt, followed by
    # an I/O failure while the fallback performs its second rename.
    with patch(
        "mailbox.os.rename",
        side_effect=[
            FileExistsError(errno.EEXIST, "target exists"),
            OSError(errno.EIO, "injected second rename failure"),
        ],
    ):
        try:
            box.flush()
        except OSError as error:
            print("flush:", type(error).__name__, error.errno)

    print("mailbox exists:", os.path.exists(path))
    print(
        "temporary files:",
        [os.path.basename(name) for name in glob.glob(path + ".*")],
    )

Observed on a Windows CPython build:

flush: OSError 5
mailbox exists: False
temporary files: ['mailbox.1788174401.yuu.26684']
Actual Behavior

After the first os.rename() raises FileExistsError, flush() removes the existing mailbox pathname. If the second os.rename() then fails, the exception propagates after the original mailbox has already been removed.

The rewritten temporary file normally remains, but the mailbox is no longer available at its original pathname.

Expected Behavior

If replacement of the rewritten mailbox fails, the original mailbox pathname should remain intact. Replacing an existing mailbox should not require explicitly removing the destination before installing the rewritten file.

CPython versions tested on

CPython main branch

Operating systems tested on

Windows

Linked PRs
  • gh-156700
主要語言
Python
星號
77.2k
分支
36k
平均合併
1 天 9 小時
30 天內合併 PR
558

貢獻指南

開啟貢獻指南

從這裡開始

  1. 先讀完整個 Issue,再讀專案的貢獻指南。
  2. 在 Issue 下留言說明你要接手 —— 這能避免兩個人做同樣的事。
  3. Fork 儲存庫,在一個分支上完成修改。
  4. 送出 Pull Request,並在描述裡引用這個 Issue 編號。

python/cpython 的其他 Issue

查看 python/cpython 的全部 Issue

相似的 Issue

更多 Python Issue

把新 issue 寄到你的電子郵件信箱

精選適合新手參與的 GitHub issue 摘要。