From 9ca6ba11994687997492c78b967b1ec70fea6d22 Mon Sep 17 00:00:00 2001 From: Tony Mountifield Date: Wed, 3 Apr 2024 17:37:35 +0100 Subject: [PATCH 1/4] gh-117467: Add preserving of mailbox owner on flush --- Lib/mailbox.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Lib/mailbox.py b/Lib/mailbox.py index 746811bd559412..229d55bd00c2e0 100644 --- a/Lib/mailbox.py +++ b/Lib/mailbox.py @@ -750,9 +750,13 @@ def flush(self): _sync_close(new_file) # self._file is about to get replaced, so no need to sync. self._file.close() - # Make sure the new file's mode is the same as the old file's - mode = os.stat(self._path).st_mode - os.chmod(new_file.name, mode) + # Make sure the new file's mode and owner are the same as the old file's + info = os.stat(self._path) + os.chmod(new_file.name, info.st_mode) + try: + os.chown(new_file.name, info.st_uid, info.st_gid) + except: + pass try: os.rename(new_file.name, self._path) except FileExistsError: From 10d58c47dba53251992ae7c26ee91de27018e8eb Mon Sep 17 00:00:00 2001 From: Tony Mountifield Date: Wed, 3 Apr 2024 18:08:45 +0100 Subject: [PATCH 2/4] Be specific about exceptions to ignore --- Lib/mailbox.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/mailbox.py b/Lib/mailbox.py index 229d55bd00c2e0..b00d9e8634c785 100644 --- a/Lib/mailbox.py +++ b/Lib/mailbox.py @@ -755,7 +755,7 @@ def flush(self): os.chmod(new_file.name, info.st_mode) try: os.chown(new_file.name, info.st_uid, info.st_gid) - except: + except (AttributeError, OSError): pass try: os.rename(new_file.name, self._path) From 3cd29745818000f6106e1697e8e6e4e71c91085e Mon Sep 17 00:00:00 2001 From: Tony Mountifield Date: Wed, 3 Apr 2024 18:37:47 +0100 Subject: [PATCH 3/4] Add NEWS entry --- .../next/Library/2024-04-03-18-36-53.gh-issue-117467.l6rWlj.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2024-04-03-18-36-53.gh-issue-117467.l6rWlj.rst diff --git a/Misc/NEWS.d/next/Library/2024-04-03-18-36-53.gh-issue-117467.l6rWlj.rst b/Misc/NEWS.d/next/Library/2024-04-03-18-36-53.gh-issue-117467.l6rWlj.rst new file mode 100644 index 00000000000000..64ae9ff7b2f0b5 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-04-03-18-36-53.gh-issue-117467.l6rWlj.rst @@ -0,0 +1,2 @@ +Preserve mailbox ownership when rewriting in :func:`mailbox.mbox.flush`. +Patch by Tony Mountifield. From c78971c7da87b6e511ea50d509a409c2febd291c Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 3 Apr 2024 22:41:19 +0300 Subject: [PATCH 4/4] Add test. --- Lib/test/test_mailbox.py | 42 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/Lib/test/test_mailbox.py b/Lib/test/test_mailbox.py index bb24d5db1f9ed4..a1d72aed9d8939 100644 --- a/Lib/test/test_mailbox.py +++ b/Lib/test/test_mailbox.py @@ -9,6 +9,7 @@ import io import tempfile from test import support +from test.support import import_helper from test.support import os_helper from test.support import refleak_helper from test.support import socket_helper @@ -1081,6 +1082,47 @@ def test_permissions_after_flush(self): self.assertEqual(os.stat(self._path).st_mode, mode) + @unittest.skipUnless(hasattr(os, 'chown'), 'requires os.chown') + def test_ownership_after_flush(self): + # See issue gh-117467 + + pwd = import_helper.import_module('pwd') + grp = import_helper.import_module('grp') + st = os.stat(self._path) + + for e in pwd.getpwall(): + if e.pw_uid != st.st_uid: + other_uid = e.pw_uid + break + else: + self.skipTest("test needs more than one user") + + for e in grp.getgrall(): + if e.gr_gid != st.st_gid: + other_gid = e.gr_gid + break + else: + self.skipTest("test needs more than one group") + + try: + os.chown(self._path, other_uid, other_gid) + except OSError: + self.skipTest('test needs root privilege') + # Change permissions as in test_permissions_after_flush. + mode = st.st_mode | 0o666 + os.chmod(self._path, mode) + + self._box.add(self._template % 0) + i = self._box.add(self._template % 1) + # Need to remove one message to make flush() create a new file + self._box.remove(i) + self._box.flush() + + st = os.stat(self._path) + self.assertEqual(st.st_uid, other_uid) + self.assertEqual(st.st_gid, other_gid) + self.assertEqual(st.st_mode, mode) + class _TestMboxMMDF(_TestSingleFile):