From patchwork Mon Mar 16 18:51:00 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 2032 Return-Path: X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1773687177; bh=YtzpzQImW3Cgm9rTNNSsPPDCwBAB5Ro5x/SU1i/xgTo=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=kuT6u0fNJhVpijd/+jKUMaVIvIW94i4Qc28kyCe2/ixZBI0sqMNr7TOJJLCzbjEkE mT6pwxfWFOMzpITLNaFUvzVARN8Dbwmx+r2i184hzlW88M8CN/UWEWznKuwxp/CPOm XFvbqbeIwGOAYFtl73t44gW55WZEjTUg06OA1Z74gHqvjxhiusAMRyvaYGD25ReMbz OdJyEJsTtGFROwTyCTpfWLh+nL96OKNsorpOfv3utd6yRN8PowaR3QFjbYFFENcq11 YJBs1l1I4JVj/qKmcffHdWLTQTtcfMwfHDW4H2cgm8qWg0G3Br0TVzW4xKv4BwrtMs yf4h373jJLDyA== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 6F1456A0B0 for ; Mon, 16 Mar 2026 12:52:57 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id aIq9pIo_-CoG for ; Mon, 16 Mar 2026 12:52:57 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1773687177; bh=YtzpzQImW3Cgm9rTNNSsPPDCwBAB5Ro5x/SU1i/xgTo=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=kuT6u0fNJhVpijd/+jKUMaVIvIW94i4Qc28kyCe2/ixZBI0sqMNr7TOJJLCzbjEkE mT6pwxfWFOMzpITLNaFUvzVARN8Dbwmx+r2i184hzlW88M8CN/UWEWznKuwxp/CPOm XFvbqbeIwGOAYFtl73t44gW55WZEjTUg06OA1Z74gHqvjxhiusAMRyvaYGD25ReMbz OdJyEJsTtGFROwTyCTpfWLh+nL96OKNsorpOfv3utd6yRN8PowaR3QFjbYFFENcq11 YJBs1l1I4JVj/qKmcffHdWLTQTtcfMwfHDW4H2cgm8qWg0G3Br0TVzW4xKv4BwrtMs yf4h373jJLDyA== Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 5E4B66A09F for ; Mon, 16 Mar 2026 12:52:57 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1773687176; bh=bLDOjHx/KyNn+OdmytGHSQqXvjpV/GGROWdOPVy8Xi4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=PVC+dTZSfZeGz63aqcbX5UqBZdBOBZugn+XGc/X1bSG8lK3b7Un5dOnTTps8UZFcs TQAZDURi0ArxL7MPkpQE2dVabwHQte61sDzjQ90MUOJCfBVFz6+fMBgmDEyKmvPGJ9 Cmcfxf1H6f8vDJibKfTxOnDQD63EFrJbcxmiv+6kKKS3JPnTB7yfRN7ojkQYb5K5M0 0S7mQzboITGs7CYm4Wb4kJHdpNX9UjM4g90DQfw50Wn1IdxM+4hfSLCrXUGdEj2gXI SVSIDerNPiU7xxRLqYObiZov66FZB8y3vjwRPVHYHqUYPt2NjTMl9jwgl6XfZ/6z8E gazJsVp2IalFg== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 423026A09B; Mon, 16 Mar 2026 12:52:56 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10026) with ESMTP id h2sxIcawLfLh; Mon, 16 Mar 2026 12:52:56 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1773687174; bh=Q9ydbynPUCV6ZK/HatoDOguMRWjnbeYMU4yqTsuBCFE=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Hq2AZNnEL1T2YSVR/B6F79wdm+1Vqq8R26vSc3cVDaWvkgeFPaRL1crKQIRx5fl2y RSKELmdp5WPjyPDXXC9APabx/zDSOXdZliLzq3rfQFnSuEtOE2FUEGSz7JoaLdd7Tp uT3ZctXgMPpgLTeB9my1O/gCRRswYTemk+1GsLeHufeRmmVnXpcwD/tCv73QDWMIMV 4KwN4vsJBBeyzcDaZB2GRSSs4eSaTRpfhHbkyOofENFoyIWxWmFjuaegK0Sum/ZitX cVgwrpbO7E7Jg8+dvZ0/Sz3Btv0oo9GsxUVm87VhbxvKTLogtvGl3iykBx7ujqP/zW qJAD+UeCdnKOA== Received: from u-boot.org (unknown [73.34.74.121]) by mail.u-boot.org (Postfix) with ESMTPSA id EBFD86A0A6; Mon, 16 Mar 2026 12:52:53 -0600 (MDT) From: Simon Glass To: U-Boot Concept Date: Mon, 16 Mar 2026 12:51:00 -0600 Message-ID: <20260316185102.3892597-3-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260316185102.3892597-1-sjg@u-boot.org> References: <20260316185102.3892597-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: 6HBTOOFKE2ZALKA65TPE64DZQC2MQVYQ X-Message-ID-Hash: 6HBTOOFKE2ZALKA65TPE64DZQC2MQVYQ X-MailFrom: sjg@u-boot.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 2/2] pickman: Only treat missing table as fresh database List-Id: Discussion and patches related to U-Boot Concept Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: From: Simon Glass get_schema_version() catches OperationalError and returns 0 to indicate a fresh database. However, it catches all OperationalError variants, including "attempt to write a readonly database" and "database is locked". When this happens, migrate_to() misinterprets an existing database as empty and re-runs all migrations from scratch, destroying the data. Narrow the catch to only match "no such table", which is the genuine fresh-database case. All other OperationalError variants are re-raised so that callers see the real problem instead of silently losing data. Signed-off-by: Simon Glass --- tools/pickman/database.py | 14 ++++++++++++-- tools/pickman/ftest.py | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/tools/pickman/database.py b/tools/pickman/database.py index 317668a979d..28b28896cb2 100644 --- a/tools/pickman/database.py +++ b/tools/pickman/database.py @@ -190,14 +190,24 @@ class Database: # pylint: disable=too-many-public-methods def get_schema_version(self): """Get the version of the database's schema + Only returns 0 when the schema_version table genuinely does not + exist (i.e. a fresh database). Other errors (read-only, locked, + corrupt) are re-raised so that migrate_to() does not accidentally + destroy an existing database by re-running all migrations. + Return: int: Database version, 0 means there is no data + + Raises: + sqlite3.OperationalError: For errors other than a missing table """ try: self.cur.execute('SELECT version FROM schema_version') return self.cur.fetchone()[0] - except sqlite3.OperationalError: - return 0 + except sqlite3.OperationalError as exc: + if 'no such table' in str(exc): + return 0 + raise def execute(self, query, parameters=()): """Execute a database query diff --git a/tools/pickman/ftest.py b/tools/pickman/ftest.py index 52c807cf8cc..261ca4cd2d5 100644 --- a/tools/pickman/ftest.py +++ b/tools/pickman/ftest.py @@ -10,6 +10,7 @@ import asyncio import argparse import os import shutil +import sqlite3 import subprocess import sys import tempfile @@ -398,6 +399,39 @@ class TestDatabase(unittest.TestCase): self.assertEqual(sources[1], ('branch-b', 'def456')) dbs.close() + def test_schema_version_readonly_raises(self): + """Test that a read-only database raises instead of returning 0. + + Previously get_schema_version() caught all OperationalError and + returned 0, which caused migrate_to() to re-create all tables on + top of an existing (read-only) database, wiping the data. + """ + with terminal.capture(): + dbs = database.Database(self.db_path) + dbs.start() + dbs.source_set('us/next', 'abc123') + dbs.commit() + + # Simulate a read-only error by replacing the cursor + real_cur = dbs.cur + mock_cur = mock.MagicMock() + mock_cur.execute.side_effect = sqlite3.OperationalError( + 'attempt to write a readonly database') + dbs.cur = mock_cur + with self.assertRaises(sqlite3.OperationalError): + dbs.get_schema_version() + dbs.cur = real_cur + dbs.close() + + def test_schema_version_missing_table_returns_zero(self): + """Test that a missing schema_version table returns 0.""" + with terminal.capture(): + dbs = database.Database(self.db_path) + dbs.open_it() + # Fresh database has no tables, should return 0 + self.assertEqual(dbs.get_schema_version(), 0) + dbs.close() + class TestDatabaseCommit(unittest.TestCase): """Tests for Database commit functions."""