Skip to content

plugins: mariabackup python-mysqlclient ignore invalid utf8#2546

Merged
BareosBot merged 5 commits intobareos:masterfrom
philfry:mysqldb-decode
Feb 25, 2026
Merged

plugins: mariabackup python-mysqlclient ignore invalid utf8#2546
BareosBot merged 5 commits intobareos:masterfrom
philfry:mysqldb-decode

Conversation

@philfry
Copy link
Contributor

@philfry philfry commented Feb 18, 2026

Hi.

I found that bareos-fd-mariabackup failed to get the innodb status from time to time, as show engine innodb status returned a not utf-8 conform output. To circumvent this, I made decode() ignore non-decodeable chars (see #2527) and everything was fine.

Now I installed the python-mysqlclient library which bareos-fd-mariabackup makes use of. And the backup fails again, this time at cursor.execute("SHOW ENGINE INNODB STATUS"). Reason enough to take a look at the database.

First, the command outputs something like

------------------------
LATEST DETECTED DEADLOCK
------------------------
2026-02-17 11:24:02 0x7f00e43a5700
*** (1) TRANSACTION:
TRANSACTION 176136490, ACTIVE 4 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 77 lock struct(s), heap size 8224, 76 row lock(s), undo log entries 106                                                                             
MariaDB thread id 19579, OS thread handle 139641805756160, query id 4113379 localhost icingadb Update                                                         
INSERT INTO "sla_history_downtime" ("downtime_start", "downtime_id", "environment_id", "endpoint_id", "object_type", "host_id", "service_id", "downtime_end") V
ALUES (1771327428000,'\?\?\"\?L~\?*\?X)\?\?^Zb^Q.\'','^Q\?l\?\?\?\?\?Y\?ˇ\?\?\?\?\?\?\?d','\?\?\?5\?\?\?\?\?\?????~\?\?^Pp^]','service',
[… and more of this stuff]

The table in question comes from icingadb, see schema.sql.

So long story short, if there are events (like deadlocks) on tables with fields of type binary, show engine innodb status may fail to decode, either using the MySQLdb library or check_output().

This PR shows a possible workaround by deactivating unicode when connecting via MySQLdb and decoding it afterwards (again, using errors='ignore').

Comments welcome.

Thank you for contributing to the Bareos Project!

Please check

  • Short description and the purpose of this PR is present above this paragraph
  • Your name is present in the AUTHORS file (optional)

If you have any questions or problems, please give a comment in the PR.

Helpful documentation and best practices

Checklist for the reviewer of the PR (will be processed by the Bareos team)

Make sure you check/merge the PR using devtools/pr-tool to have some simple automated checks run and a proper changelog record added.

General
  • Is the PR title usable as CHANGELOG entry?
  • Purpose of the PR is understood
  • Commit descriptions are understandable and well formatted
  • Required backport PRs have been created
  • Correct milestone is set
Source code quality
  • Source code changes are understandable
  • Variable and function names are meaningful
  • Code comments are correct (logically and spelling)
  • Required documentation changes are present and part of the PR
Tests
  • Decision taken that a test is required (if not, then remove this paragraph)
  • The choice of the type of test (unit test or systemtest) is reasonable
  • Testname matches exactly what is being tested
  • On a fail, output of the test leads quickly to the origin of the fault

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 18, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 685fbe9 and 0687866.

📒 Files selected for processing (3)
  • CHANGELOG.md
  • core/src/plugins/filed/python/mariabackup/bareos-fd-mariabackup.py
  • core/src/plugins/filed/python/percona-xtrabackup/bareos-fd-percona-xtrabackup.py

📝 Walkthrough

Walkthrough

Both MariaDB and Percona XtraBackup FD plugins now connect with use_unicode=False, read the INNODB status into raw_status with a None check (fatal if NULL), decode raw_status using errors="ignore", and ensure the DB connection is closed in a finally block before processing.

Changes

Cohort / File(s) Summary
MariaDB backup plugin
core/src/plugins/filed/python/mariabackup/bareos-fd-mariabackup.py
Added use_unicode=False to MySQLdb.connect(); store result[0][2] in raw_status, check for None and emit fatal error if NULL, decode bytes via .decode(errors="ignore") into innodb_status, and close connection in a finally block.
Percona XtraBackup plugin
core/src/plugins/filed/python/percona-xtrabackup/bareos-fd-percona-xtrabackup.py
Same changes as MariaDB: use_unicode=False on connect; capture raw_status from result[0][2], check for None and fatal on NULL, decode raw bytes with .decode(errors="ignore"), and ensure connection closed in finally.
Changelog
CHANGELOG.md
Added Unreleased entry: "plugins: mariabackup python-mysqlclient ignore invalid utf8 [PR #2546]".

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

Suggested reviewers

  • bruno-at-bareos

Poem

I nibble logs beneath the moonlit stack,
raw_status cradled, no stray bytes come back,
decode the whispers, ignore the mess,
use_unicode off — tidy and bless,
a rabbit hops off with a grateful snack 🐇

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title concisely and accurately describes the main change: addressing UTF-8 decoding issues in the mariabackup plugin by ignoring invalid UTF-8 characters when using python-mysqlclient.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Description check ✅ Passed The PR description comprehensively explains the issue, root cause, workaround, and references related ticket #2527. All contributor checklist items are marked complete.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 24, 2026

Note

Docstrings generation - SUCCESS
Generated docstrings for this pull request at #2557

coderabbitai bot added a commit that referenced this pull request Feb 24, 2026
Docstrings generation was requested by @bruno-at-bareos.

* #2546 (comment)

The following files were modified:

* `core/src/plugins/filed/python/mariabackup/bareos-fd-mariabackup.py`
@bruno-at-bareos bruno-at-bareos self-requested a review February 24, 2026 15:21
@bruno-at-bareos bruno-at-bareos self-assigned this Feb 24, 2026
@bruno-at-bareos bruno-at-bareos modified the milestone: 26.0.0 Feb 24, 2026
Copy link
Contributor

@bruno-at-bareos bruno-at-bareos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, Thanks a lot for the proposal.
To be able to validate the PR, I would like to see two small changes. See change proposal in code.
and also our pr-tools check complain about the length of the commit title.

ea0af9d24 bareos-fd-mariabackup: ignore invalid utf8 when retrieving innodb status using MySQLdb: headline too long (86 > 60)

see developer documentation for explanation.
Could you rebase and push force (we only have one commit here)
plugins: mariabackup python-mysqlclient ignore invalid utf8

@bruno-at-bareos bruno-at-bareos changed the title bareos-fd-mariabackup: ignore invalid utf8 with python-mysqlclient plugins: mariabackup ignore invalid utf8 python-mysqlclient Feb 24, 2026
@bruno-at-bareos bruno-at-bareos changed the title plugins: mariabackup ignore invalid utf8 python-mysqlclient plugins: mariabackup python-mysqlclient ignore invalid utf8 Feb 24, 2026
This patch fixes situations where retrieving the innodb status using
MySQLdb results in non-utf8-decodeable data.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
core/src/plugins/filed/python/mariabackup/bareos-fd-mariabackup.py (1)

313-323: ⚠️ Potential issue | 🟡 Minor

Fix approach is correct; add a None guard before .decode()

use_unicode=False ensures MySQLdb returns raw bytes for the Status column, and .decode(errors="ignore") then safely strips non-UTF-8 sequences — this correctly mirrors the get_lsn_by_command() path already in place (line 195).

One small gap: if result[0][2] is NULL in the database (unlikely but technically possible), .decode(errors="ignore") raises an AttributeError. The existing empty-result guard on line 317 doesn't cover this case. Consider a minimal defensive check:

🛡️ Proposed guard
-                    innodb_status = result[0][2].decode(errors="ignore")
+                    raw = result[0][2]
+                    if raw is None:
+                        JobMessage(
+                            M_FATAL,
+                            "SHOW ENGINE INNODB STATUS returned NULL status field",
+                        )
+                        return bRC_Error
+                    innodb_status = raw.decode(errors="ignore")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/src/plugins/filed/python/mariabackup/bareos-fd-mariabackup.py` around
lines 313 - 323, The code fetches the INNODB STATUS into innodb_status by
decoding result[0][2] but doesn't guard against NULL (None) in result[0][2],
which would raise AttributeError; update the block around the MySQLdb
cursor/SHOW ENGINE INNODB STATUS fetch to check if result[0][2] is None before
calling .decode (mirror the defensive pattern used in get_lsn_by_command()), and
if it is None either treat it as an empty bytes value to decode or emit the same
JobMessage and return bRC_Error so the code never calls .decode on None.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@core/src/plugins/filed/python/mariabackup/bareos-fd-mariabackup.py`:
- Line 323: The code calls result[0][2].decode(...) without guarding for SQL
NULLs; if result[0][2] is None this raises AttributeError. Update the assignment
for innodb_status to handle None (e.g., treat None as empty bytes/string) before
calling .decode(errors="ignore") so decoding only happens on a bytes-like value;
locate the innodb_status assignment and change it to check result[0][2] (or use
a short-circuit like using a fallback) and then decode.

---

Outside diff comments:
In `@core/src/plugins/filed/python/mariabackup/bareos-fd-mariabackup.py`:
- Around line 313-323: The code fetches the INNODB STATUS into innodb_status by
decoding result[0][2] but doesn't guard against NULL (None) in result[0][2],
which would raise AttributeError; update the block around the MySQLdb
cursor/SHOW ENGINE INNODB STATUS fetch to check if result[0][2] is None before
calling .decode (mirror the defensive pattern used in get_lsn_by_command()), and
if it is None either treat it as an empty bytes value to decode or emit the same
JobMessage and return bRC_Error so the code never calls .decode on None.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ea0af9d and 4e0678e.

📒 Files selected for processing (1)
  • core/src/plugins/filed/python/mariabackup/bareos-fd-mariabackup.py

@bruno-at-bareos
Copy link
Contributor

Note to myself: port the changes to the percona-xtrabackup plugin too.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
core/src/plugins/filed/python/mariabackup/bareos-fd-mariabackup.py (1)

317-331: ⚠️ Potential issue | 🟡 Minor

Connection leak on both early-return paths inside the try block.

conn.close() at Line 331 is only reached on the happy path. Both the len(result) == 0 guard (Line 317–322, pre-existing) and the new raw_status is None guard (Lines 324–329) call return bRC_Error without closing the connection first. Since each backup job opens a fresh connection, this leaks a handle each time either guard fires.

Wrap the connection lifetime in a try/finally:

🔧 Proposed fix using try/finally for guaranteed cleanup
                 try:
-                    conn = MySQLdb.connect(**self.connect_options, use_unicode=False)
-                    cursor = conn.cursor()
-                    cursor.execute("SHOW ENGINE INNODB STATUS")
-                    result = cursor.fetchall()
-                    if len(result) == 0:
-                        JobMessage(
-                            M_FATAL,
-                            "Could not fetch SHOW ENGINE INNODB STATUS, unprivileged user?",
-                        )
-                        return bRC_Error
-                    raw_status = result[0][2]
-                    if raw_status is None:
-                        JobMessage(
-                            M_FATAL,
-                            "SHOW ENGINE INNODB STATUS returned a NULL status field",
-                        )
-                        return bRC_Error
-                    innodb_status = raw_status.decode(errors="ignore")
-                    conn.close()
+                    conn = MySQLdb.connect(**self.connect_options, use_unicode=False)
+                    try:
+                        cursor = conn.cursor()
+                        cursor.execute("SHOW ENGINE INNODB STATUS")
+                        result = cursor.fetchall()
+                        if len(result) == 0:
+                            JobMessage(
+                                M_FATAL,
+                                "Could not fetch SHOW ENGINE INNODB STATUS, unprivileged user?",
+                            )
+                            return bRC_Error
+                        raw_status = result[0][2]
+                        if raw_status is None:
+                            JobMessage(
+                                M_FATAL,
+                                "SHOW ENGINE INNODB STATUS returned a NULL status field",
+                            )
+                            return bRC_Error
+                        innodb_status = raw_status.decode(errors="ignore")
+                    finally:
+                        conn.close()
                 except Exception as e:
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/src/plugins/filed/python/mariabackup/bareos-fd-mariabackup.py` around
lines 317 - 331, The code returns early on two error paths without closing the
DB connection (conn), leaking handles; wrap the connection usage in a
try/finally so conn.close() always runs (move the existing conn.close() into a
finally block) and keep the current error returns that call JobMessage and
return bRC_Error inside the try before the finally; reference the existing
symbols conn, JobMessage, bRC_Error and the block that sets
raw_status/innodb_status to locate where to add the try/finally.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@core/src/plugins/filed/python/mariabackup/bareos-fd-mariabackup.py`:
- Around line 317-331: The code returns early on two error paths without closing
the DB connection (conn), leaking handles; wrap the connection usage in a
try/finally so conn.close() always runs (move the existing conn.close() into a
finally block) and keep the current error returns that call JobMessage and
return bRC_Error inside the try before the finally; reference the existing
symbols conn, JobMessage, bRC_Error and the block that sets
raw_status/innodb_status to locate where to add the try/finally.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4e0678e and b7e10b5.

📒 Files selected for processing (1)
  • core/src/plugins/filed/python/mariabackup/bareos-fd-mariabackup.py

bruno-at-bareos and others added 2 commits February 25, 2026 14:24
The len(result) == 0 check above covers an empty result set, but if
the Status column returns NULL (making result[0][2] a Python None),
.decode(errors="ignore") will raise an AttributeError that lands in
the bare except Exception handler and emits a confusing M_FATAL
message.

The risk is very low in practice, but it's worth a one-liner guard
given the stakes (a M_FATAL during backup):

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
This patch fixes situations where retrieving the innodb status using
MySQLdb results in non-utf8-decodeable data, like we fixed in
mariabackup plugin.

Signed-off-by: Bruno Friedmann <bruno.friedmann@bareos.com>
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 25, 2026

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
{"name":"HttpError","status":500,"request":{"method":"PATCH","url":"https://api.github.com/repos/bareos/bareos/issues/comments/3920679570","headers":{"accept":"application/vnd.github.v3+json","user-agent":"octokit.js/0.0.0-development octokit-core.js/7.0.6 Node.js/24","authorization":"token [REDACTED]","content-type":"application/json; charset=utf-8"},"body":{"body":"<!-- This is an auto-generated comment: summarize by coderabbit.ai -->\n<!-- walkthrough_start -->\n\n<details>\n<summary>📝 Walkthrough</summary>\n\n## Walkthrough\n\nBoth backup plugins now pass use_unicode=False to MySQLdb.connect, fetch SHOW ENGINE INNODB STATUS into a raw_status variable, check for NULL/missing status (fatal on NULL), and decode bytes with .decode(errors=\"ignore\") before using the INNODB status string.\n\n## Changes\n\n|Cohort / File(s)|Summary|\n|---|---|\n|**MariaDB backup plugin** <br> `core/src/plugins/filed/python/mariabackup/bareos-fd-mariabackup.py`|Added `use_unicode=False` to `MySQLdb.connect()`; assign `raw_status = result[0][2]`; check for empty/NULL results and emit fatal + return on NULL; decode `raw_status` with `.decode(errors=\"ignore\")` into `innodb_status` for further processing.|\n|**Percona XtraBackup plugin** <br> `core/src/plugins/filed/python/percona-xtrabackup/bareos-fd-percona-xtrabackup.py`|Added `use_unicode=False` to `MySQLdb.connect()`; replace direct status assignment with `raw_status = result[0][2]`; check for NULL and return error on NULL; decode `raw_status` with `.decode(errors=\"ignore\")` before assigning `info`.|\n\n## Estimated code review effort\n\n🎯 2 (Simple) | ⏱️ ~10 minutes\n\n## Poem\n\n> I twitch my nose at bytes that roam,  \n> I set use_unicode to keep them home,  \n> raw_status checked, then softly freed,  \n> decoded gently — tidy seed,  \n> the rabbit hums: backups snug as foam 🐇✨\n\n</details>\n\n<!-- walkthrough_end -->\n\n\n<!-- pre_merge_checks_walkthrough_start -->\n\n<details>\n<summary>🚥 Pre-merge checks | ✅ 3</summary>\n\n<details>\n<summary>✅ Passed checks (3 passed)</summary>\n\n|     Check name     | Status   | Explanation                                                                                                                                                                                                                                                                                                 |\n| :----------------: | :------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n|     Title check    | ✅ Passed | The title accurately reflects the main change: handling invalid UTF-8 in the mariabackup plugin when using python-mysqlclient library by adding use_unicode=False and decode error handling.                                                                                                                |\n|  Description check | ✅ Passed | The description provides a comprehensive explanation of the issue (non-UTF-8 bytes from SHOW ENGINE INNODB STATUS with BINARY fields), includes concrete examples, references related issue `#2527`, explains the workaround, and follows the template structure with all required checklist items completed. |\n| Docstring Coverage | ✅ Passed | Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.                                                                                                                                                                                                                        |\n\n</details>\n\n<sub>✏️ Tip: You can configure your own custom pre-merge checks in the settings.</sub>\n\n</details>\n\n<!-- pre_merge_checks_walkthrough_end -->\n\n<!-- finishing_touch_checkbox_start -->\n\n<details>\n<summary>✨ Finishing Touches</summary>\n\n- [ ] <!-- {\"checkboxId\": \"7962f53c-55bc-4827-bfbf-6a18da830691\"} --> 📝 Generate docstrings (stacked PR)\n- [ ] <!-- {\"checkboxId\": \"3e1879ae-f29b-4d0d-8e06-d12b7ba33d98\"} --> 📝 Generate docstrings (commit on current branch)\n<details>\n<summary>🧪 Generate unit tests (beta)</summary>\n\n- [ ] <!-- {\"checkboxId\": \"f47ac10b-58cc-4372-a567-0e02b2c3d479\", \"radioGroupId\": \"utg-output-choice-group-unknown_comment_id\"} -->   Create PR with unit tests\n- [ ] <!-- {\"checkboxId\": \"07f1e7d6-8a8e-4e23-9900-8731c2c87f58\", \"radioGroupId\": \"utg-output-choice-group-unknown_comment_id\"} -->   Post copyable unit tests in a comment\n\n</details>\n\n</details>\n\n<!-- finishing_touch_checkbox_end -->\n\n<!-- tips_start -->\n\n---\n\n\n\n<sub>Comment `@coderabbitai help` to get the list of available commands and usage tips.</sub>\n\n<!-- tips_end -->\n\n<!-- internal state start -->\n\n\n<!-- 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 -->\n\n<!-- internal state end -->"},"request":{"retryCount":3,"retries":3,"retryAfter":16}}}

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
core/src/plugins/filed/python/percona-xtrabackup/bareos-fd-percona-xtrabackup.py (1)

224-241: LGTM — logic is correct; consider guarding conn.close() with finally

The chain use_unicode=False → bytes result → None-guard → .decode(errors="ignore") is correct and mirrors the mariabackup fix. bytes.decode() defaults to UTF-8, which is exactly the right encoding to strip invalid bytes from SHOW ENGINE INNODB STATUS output.

One pre-existing structural nit: if cursor.execute() or fetchall() raises, conn.close() is skipped (the outer except re-raises as bRC_Error without closing). The connection will eventually be GC'd, but an explicit finally is cleaner.

♻️ Optional: guard conn.close() with try/finally
-                try:
-                    conn = MySQLdb.connect(**self.connect_options, use_unicode=False)
-                    cursor = conn.cursor()
-                    cursor.execute("SHOW ENGINE INNODB STATUS")
-                    result = cursor.fetchall()
-                    if len(result) == 0:
-                        JobMessage(
-                            M_FATAL,
-                            "Could not fetch SHOW ENGINE INNODB STATUS, unprivileged user?",
-                        )
-                        return bRC_Error
-                    raw_status = result[0][2]
-                    if raw_status is None:
-                        JobMessage(
-                            M_FATAL,
-                            "SHOW ENGINE INNODB STATUS returned a NULL status field",
-                        )
-                        return bRC_Error
-                    info = raw_status.decode(errors="ignore")
-                    conn.close()
-                except Exception as e:
+                conn = None
+                try:
+                    conn = MySQLdb.connect(**self.connect_options, use_unicode=False)
+                    cursor = conn.cursor()
+                    cursor.execute("SHOW ENGINE INNODB STATUS")
+                    result = cursor.fetchall()
+                    if len(result) == 0:
+                        JobMessage(
+                            M_FATAL,
+                            "Could not fetch SHOW ENGINE INNODB STATUS, unprivileged user?",
+                        )
+                        return bRC_Error
+                    raw_status = result[0][2]
+                    if raw_status is None:
+                        JobMessage(
+                            M_FATAL,
+                            "SHOW ENGINE INNODB STATUS returned a NULL status field",
+                        )
+                        return bRC_Error
+                    info = raw_status.decode(errors="ignore")
+                except Exception as e:
                     JobMessage(
                         M_FATAL,
                         "Could not get LSN, Error: %s" % e,
                     )
                     return bRC_Error
+                finally:
+                    if conn is not None:
+                        conn.close()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@core/src/plugins/filed/python/percona-xtrabackup/bareos-fd-percona-xtrabackup.py`
around lines 224 - 241, The DB connection is not closed if cursor.execute() or
cursor.fetchall() raises; wrap the work that uses conn and cursor in a
try/finally so conn.close() always runs. Specifically, after creating conn =
MySQLdb.connect(**self.connect_options, use_unicode=False) and cursor =
conn.cursor(), move the execute/fetchall/result handling into a try block and
call conn.close() (and cursor.close() if desired) in the finally block; keep
existing JobMessage and bRC_Error returns unchanged and ensure the finally runs
before any returns.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@core/src/plugins/filed/python/percona-xtrabackup/bareos-fd-percona-xtrabackup.py`:
- Around line 224-241: The DB connection is not closed if cursor.execute() or
cursor.fetchall() raises; wrap the work that uses conn and cursor in a
try/finally so conn.close() always runs. Specifically, after creating conn =
MySQLdb.connect(**self.connect_options, use_unicode=False) and cursor =
conn.cursor(), move the execute/fetchall/result handling into a try block and
call conn.close() (and cursor.close() if desired) in the finally block; keep
existing JobMessage and bRC_Error returns unchanged and ensure the finally runs
before any returns.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b7e10b5 and 6f6345a.

📒 Files selected for processing (1)
  • core/src/plugins/filed/python/percona-xtrabackup/bareos-fd-percona-xtrabackup.py

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
core/src/plugins/filed/python/percona-xtrabackup/bareos-fd-percona-xtrabackup.py (1)

241-245: ⚠️ Potential issue | 🟠 Major

Guard last_lsn when no parseable LSN line is found.

If Lines 243-245 never match, last_lsn is unset and Line 280 can raise UnboundLocalError.

🐛 Proposed fix
-                    info = raw_status.decode(errors="ignore")
+                    info = raw_status.decode(errors="ignore")
                     conn.close()
+                    last_lsn = None
                     for line in info.split("\n"):
                         if line.startswith("Log sequence number"):
                             last_lsn = int(line.split(" ")[-1])
+                    if last_lsn is None:
+                        JobMessage(
+                            M_FATAL,
+                            "Could not parse Log sequence number from INNODB status",
+                        )
+                        return bRC_Error

Also applies to: 280-280

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@core/src/plugins/filed/python/percona-xtrabackup/bareos-fd-percona-xtrabackup.py`
around lines 241 - 245, The loop parsing raw_status may never set last_lsn,
causing an UnboundLocalError later when it’s used; initialize last_lsn (e.g., to
None or 0) before the for loop that processes info (where raw_status.decode and
conn.close() are called) and then check/guard its value before it’s consumed
(the later usage around last_lsn at line ~280) — if last_lsn is None/invalid,
raise a clear error or handle the missing LSN path accordingly in the
surrounding function.
🧹 Nitpick comments (2)
core/src/plugins/filed/python/mariabackup/bareos-fd-mariabackup.py (1)

313-331: Close the MySQL connection on all exit paths.

On Line 317 and Line 324, the function can return before Line 331, leaving the DB connection open.

♻️ Proposed fix
                 try:
                     conn = MySQLdb.connect(**self.connect_options, use_unicode=False)
-                    cursor = conn.cursor()
-                    cursor.execute("SHOW ENGINE INNODB STATUS")
-                    result = cursor.fetchall()
-                    if len(result) == 0:
-                        JobMessage(
-                            M_FATAL,
-                            "Could not fetch SHOW ENGINE INNODB STATUS, unprivileged user?",
-                        )
-                        return bRC_Error
-                    raw_status = result[0][2]
-                    if raw_status is None:
-                        JobMessage(
-                            M_FATAL,
-                            "SHOW ENGINE INNODB STATUS returned a NULL status field",
-                        )
-                        return bRC_Error
-                    innodb_status = raw_status.decode(errors="ignore")
-                    conn.close()
+                    try:
+                        cursor = conn.cursor()
+                        cursor.execute("SHOW ENGINE INNODB STATUS")
+                        result = cursor.fetchall()
+                        if len(result) == 0:
+                            JobMessage(
+                                M_FATAL,
+                                "Could not fetch SHOW ENGINE INNODB STATUS, unprivileged user?",
+                            )
+                            return bRC_Error
+                        raw_status = result[0][2]
+                        if raw_status is None:
+                            JobMessage(
+                                M_FATAL,
+                                "SHOW ENGINE INNODB STATUS returned a NULL status field",
+                            )
+                            return bRC_Error
+                        innodb_status = raw_status.decode(errors="ignore")
+                    finally:
+                        conn.close()
                 except Exception as e:
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/src/plugins/filed/python/mariabackup/bareos-fd-mariabackup.py` around
lines 313 - 331, The MySQL connection opened via MySQLdb.connect (stored in
conn) may be left open when early returns happen after checking result length or
raw_status; ensure conn is always closed by wrapping the connect/cursor/execute
block in a try/finally (or use a context manager) and call conn.close() in the
finally block, or explicitly close conn before each return in the code around
the SHOW ENGINE INNODB STATUS logic (references: MySQLdb.connect, conn, cursor,
JobMessage, innodb_status).
core/src/plugins/filed/python/percona-xtrabackup/bareos-fd-percona-xtrabackup.py (1)

224-242: Ensure DB connection is closed on error returns.

Returns on Line 228 and Line 235/240 bypass conn.close() on Line 242.

♻️ Proposed fix
             if hasMySQLdbModule:
                 try:
                     conn = MySQLdb.connect(**self.connect_options, use_unicode=False)
-                    cursor = conn.cursor()
-                    cursor.execute("SHOW ENGINE INNODB STATUS")
-                    result = cursor.fetchall()
-                    if len(result) == 0:
-                        JobMessage(
-                            M_FATAL,
-                            "Could not fetch SHOW ENGINE INNODB STATUS, unprivileged user?",
-                        )
-                        return bRC_Error
-                    raw_status = result[0][2]
-                    if raw_status is None:
-                        JobMessage(
-                            M_FATAL,
-                            "SHOW ENGINE INNODB STATUS returned a NULL status field",
-                        )
-                        return bRC_Error
-                    info = raw_status.decode(errors="ignore")
-                    conn.close()
+                    try:
+                        cursor = conn.cursor()
+                        cursor.execute("SHOW ENGINE INNODB STATUS")
+                        result = cursor.fetchall()
+                        if len(result) == 0:
+                            JobMessage(
+                                M_FATAL,
+                                "Could not fetch SHOW ENGINE INNODB STATUS, unprivileged user?",
+                            )
+                            return bRC_Error
+                        raw_status = result[0][2]
+                        if raw_status is None:
+                            JobMessage(
+                                M_FATAL,
+                                "SHOW ENGINE INNODB STATUS returned a NULL status field",
+                            )
+                            return bRC_Error
+                        info = raw_status.decode(errors="ignore")
+                    finally:
+                        conn.close()
                     for line in info.split("\n"):
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@core/src/plugins/filed/python/percona-xtrabackup/bareos-fd-percona-xtrabackup.py`
around lines 224 - 242, The code opens a DB connection via MySQLdb.connect into
conn and may return bRC_Error on missing results or NULL raw_status without
closing conn; wrap the connection/cursor usage in a try/finally (or context
manager) around the block using conn/cursor so conn.close() always runs, or
explicitly call conn.close() immediately before each early return in the section
that queries "SHOW ENGINE INNODB STATUS" (references: MySQLdb.connect, conn,
cursor, cursor.execute, result/raw_status, JobMessage, bRC_Error, info). Ensure
conn.close() is executed even when JobMessage is invoked and bRC_Error is
returned.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In
`@core/src/plugins/filed/python/percona-xtrabackup/bareos-fd-percona-xtrabackup.py`:
- Around line 241-245: The loop parsing raw_status may never set last_lsn,
causing an UnboundLocalError later when it’s used; initialize last_lsn (e.g., to
None or 0) before the for loop that processes info (where raw_status.decode and
conn.close() are called) and then check/guard its value before it’s consumed
(the later usage around last_lsn at line ~280) — if last_lsn is None/invalid,
raise a clear error or handle the missing LSN path accordingly in the
surrounding function.

---

Nitpick comments:
In `@core/src/plugins/filed/python/mariabackup/bareos-fd-mariabackup.py`:
- Around line 313-331: The MySQL connection opened via MySQLdb.connect (stored
in conn) may be left open when early returns happen after checking result length
or raw_status; ensure conn is always closed by wrapping the
connect/cursor/execute block in a try/finally (or use a context manager) and
call conn.close() in the finally block, or explicitly close conn before each
return in the code around the SHOW ENGINE INNODB STATUS logic (references:
MySQLdb.connect, conn, cursor, JobMessage, innodb_status).

In
`@core/src/plugins/filed/python/percona-xtrabackup/bareos-fd-percona-xtrabackup.py`:
- Around line 224-242: The code opens a DB connection via MySQLdb.connect into
conn and may return bRC_Error on missing results or NULL raw_status without
closing conn; wrap the connection/cursor usage in a try/finally (or context
manager) around the block using conn/cursor so conn.close() always runs, or
explicitly call conn.close() immediately before each early return in the section
that queries "SHOW ENGINE INNODB STATUS" (references: MySQLdb.connect, conn,
cursor, cursor.execute, result/raw_status, JobMessage, bRC_Error, info). Ensure
conn.close() is executed even when JobMessage is invoked and bRC_Error is
returned.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b7e10b5 and 685fbe9.

📒 Files selected for processing (2)
  • core/src/plugins/filed/python/mariabackup/bareos-fd-mariabackup.py
  • core/src/plugins/filed/python/percona-xtrabackup/bareos-fd-percona-xtrabackup.py

To avoid any connection leak on both early-return paths inside the
try block, we enclose them in a try / finally block.

Signed-off-by: Bruno Friedmann <bruno.friedmann@bareos.com>
Copy link
Contributor

@bruno-at-bareos bruno-at-bareos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the whole improvement.

@BareosBot BareosBot merged commit b0bc41a into bareos:master Feb 25, 2026
0 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants