mirror of
https://github.com/python/cpython.git
synced 2025-12-31 04:23:37 +00:00
[3.14] gh-135386: Fix "unable to open database file" errors on readonly DB (GH-135566) (GH-138056)
Add immutable=1 flag for read-only SQLite access to avoid WAL/SHM errors on readonly DB.
(cherry picked from commit c0ae92b7c0)
Co-authored-by: General_K1ng <generak1ng0@gmail.com>
Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
Co-authored-by: Petr Viktorin <encukou@gmail.com>
This commit is contained in:
parent
51dfc67092
commit
8ab7081015
3 changed files with 62 additions and 5 deletions
|
|
@ -59,18 +59,22 @@ def __init__(self, path, /, *, flag, mode):
|
|||
# We use the URI format when opening the database.
|
||||
uri = _normalize_uri(path)
|
||||
uri = f"{uri}?mode={flag}"
|
||||
if flag == "ro":
|
||||
# Add immutable=1 to allow read-only SQLite access even if wal/shm missing
|
||||
uri += "&immutable=1"
|
||||
|
||||
try:
|
||||
self._cx = sqlite3.connect(uri, autocommit=True, uri=True)
|
||||
except sqlite3.Error as exc:
|
||||
raise error(str(exc))
|
||||
|
||||
# This is an optimization only; it's ok if it fails.
|
||||
with suppress(sqlite3.OperationalError):
|
||||
self._cx.execute("PRAGMA journal_mode = wal")
|
||||
if flag != "ro":
|
||||
# This is an optimization only; it's ok if it fails.
|
||||
with suppress(sqlite3.OperationalError):
|
||||
self._cx.execute("PRAGMA journal_mode = wal")
|
||||
|
||||
if flag == "rwc":
|
||||
self._execute(BUILD_TABLE)
|
||||
if flag == "rwc":
|
||||
self._execute(BUILD_TABLE)
|
||||
|
||||
def _execute(self, *args, **kwargs):
|
||||
if not self._cx:
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import os
|
||||
import stat
|
||||
import sys
|
||||
import unittest
|
||||
from contextlib import closing
|
||||
|
|
@ -14,6 +16,11 @@
|
|||
from dbm.sqlite3 import _normalize_uri
|
||||
|
||||
|
||||
root_in_posix = False
|
||||
if hasattr(os, 'geteuid'):
|
||||
root_in_posix = (os.geteuid() == 0)
|
||||
|
||||
|
||||
class _SQLiteDbmTests(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
|
@ -90,6 +97,50 @@ def test_readonly_iter(self):
|
|||
self.assertEqual([k for k in self.db], [b"key1", b"key2"])
|
||||
|
||||
|
||||
@unittest.skipIf(root_in_posix, "test is meanless with root privilege")
|
||||
class ReadOnlyFilesystem(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.test_dir = os_helper.TESTFN
|
||||
self.addCleanup(os_helper.rmtree, self.test_dir)
|
||||
os.mkdir(self.test_dir)
|
||||
self.db_path = os.path.join(self.test_dir, "test.db")
|
||||
|
||||
db = dbm_sqlite3.open(self.db_path, "c")
|
||||
db[b"key"] = b"value"
|
||||
db.close()
|
||||
|
||||
def test_readonly_file_read(self):
|
||||
os.chmod(self.db_path, stat.S_IREAD)
|
||||
with dbm_sqlite3.open(self.db_path, "r") as db:
|
||||
self.assertEqual(db[b"key"], b"value")
|
||||
|
||||
def test_readonly_file_write(self):
|
||||
os.chmod(self.db_path, stat.S_IREAD)
|
||||
with dbm_sqlite3.open(self.db_path, "w") as db:
|
||||
with self.assertRaises(dbm_sqlite3.error):
|
||||
db[b"newkey"] = b"newvalue"
|
||||
|
||||
def test_readonly_dir_read(self):
|
||||
os.chmod(self.test_dir, stat.S_IREAD | stat.S_IEXEC)
|
||||
with dbm_sqlite3.open(self.db_path, "r") as db:
|
||||
self.assertEqual(db[b"key"], b"value")
|
||||
|
||||
def test_readonly_dir_write(self):
|
||||
os.chmod(self.test_dir, stat.S_IREAD | stat.S_IEXEC)
|
||||
with dbm_sqlite3.open(self.db_path, "w") as db:
|
||||
try:
|
||||
db[b"newkey"] = b"newvalue"
|
||||
modified = True # on Windows and macOS
|
||||
except dbm_sqlite3.error:
|
||||
modified = False
|
||||
with dbm_sqlite3.open(self.db_path, "r") as db:
|
||||
if modified:
|
||||
self.assertEqual(db[b"newkey"], b"newvalue")
|
||||
else:
|
||||
self.assertNotIn(b"newkey", db)
|
||||
|
||||
|
||||
class ReadWrite(_SQLiteDbmTests):
|
||||
|
||||
def setUp(self):
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
Fix opening a :mod:`dbm.sqlite3` database for reading from read-only file
|
||||
or directory.
|
||||
Loading…
Add table
Add a link
Reference in a new issue