Skip to content

Better error message when trying to backup a DB to itself#8944

Open
kdmtctl wants to merge 3 commits intoFirebirdSQL:masterfrom
kdmtctl:fix-gbak-same-name-check
Open

Better error message when trying to backup a DB to itself#8944
kdmtctl wants to merge 3 commits intoFirebirdSQL:masterfrom
kdmtctl:fix-gbak-same-name-check

Conversation

@kdmtctl
Copy link

@kdmtctl kdmtctl commented Mar 14, 2026

In Firebird 2.5, gbak does not check whether the source and destination resolve to the same file during backup. Running gbak -b db_alias /path/to/db.gdb where the alias resolves to the same path silently overwrites the live database with backup data. We lost a database this way.

Trying to fix this on master, I found that Firebird 5 already catches this case in the all-pairs file comparison loop — but reports the wrong error message. Both source equals destination and duplicate destination checks matches report msg 9 ("multiple sources or destinations specified"), which is confusing while the actual problem is that the source database and backup destination resolve to the same path via alias.

This patch separates the error messages: msg 11 ("input and output have the same name") when the source matches a destination, msg 9 when duplicate destinations are specified.

What changed

In src/burp/burp.cpp, the existing all-pairs comparison loop (which already expands both sides via expandDatabaseName()) now checks whether the match involves the first file (source). If so, it emits msg 11 instead of msg 9.

Tested on par with Firebird 5.0.1.1469

Patched binary built with GCC 13.3.1 (gcc-toolset-13) in AlmaLinux 9 container, tested against a live Firebird 5.0.1 instance.

Test 1: Stock gbak — backup with alias resolving to same file as destination
Command:  gbak -b gbak_test /var/ibdata/gbak_test.fdb -user sysdba
Expected: rejected (alias and path resolve to the same file)
Result:   REJECTED, but wrong error message:
  gbak: ERROR:multiple sources or destinations specified
  Exit: 1

Test 2: Patched gbak — backup with alias resolving to same file as destination
Command:  gbak -b gbak_test /var/ibdata/gbak_test.fdb -user sysdba
Expected: rejected with msg 11
Result:   PASS
  gbak: ERROR:input and output have the same name.  Disallowed.
  Exit: 1

Test 3a: Patched gbak — split backup to separate files
Command:  gbak -b gbak_test /tmp/gbak_split_1.fbk 500m /tmp/gbak_split_2.fbk -user sysdba
Expected: success
Result:   PASS
  Exit: 0

Test 3b: Patched gbak — split backup with duplicate destination
Command:  gbak -b gbak_test /tmp/gbak_dup.fbk 500m /tmp/gbak_dup.fbk -user sysdba
Expected: rejected with msg 9
Result:   PASS
  gbak: ERROR:multiple sources or destinations specified
  Exit: 1

Test 4: Patched gbak — restore
Command:  gbak -r /tmp/gbak_test_backup.fbk /var/ibdata/gbak_restored.fdb -user sysdba
Expected: success
Result:   PASS
  Exit: 0

When gbak -b is called with a database alias as source and the resolved
path as destination (e.g., gbak -b db_alias /path/to/db.gdb), the
duplicate file check in the validation loop only compared raw strings,
missing the overlap. This could silently overwrite the live database
with backup data.

Enhance the existing all-pairs file comparison loop to expand both
paths via expandDatabaseName() (already used for one side) and emit
the correct error: msg 11 (input and output have the same name) when
source matches a destination, msg 9 (multiple sources or destinations)
for duplicate destinations.
@aafemt
Copy link
Contributor

aafemt commented Mar 14, 2026

I would suggest to add inability to open existing file for write (unless OVERWRITE is specified) as the second line of defense.

@kdmtctl
Copy link
Author

kdmtctl commented Mar 14, 2026

I would suggest to add inability to open existing file for write (unless OVERWRITE is specified) as the second line of defense.

This can be implemented by adding O_EXCL to the open() flags on Linux (burp.cpp:2224) and changing CREATE_ALWAYS to CREATE_NEW on Windows (burp.cpp:2221), then adding something like -recreate overwrite for backup mode.

But this is a breaking change and some backup scripts should be updated to this behavior.

@aafemt
Copy link
Contributor

aafemt commented Mar 14, 2026

Yes, and this is fine for master branch. Backporting to stable branches is a different story.

@kdmtctl kdmtctl changed the title Resolve database aliases before comparing input/output paths in gbak Better error message when trying to backup a DB to itself Mar 14, 2026
gbak -b now refuses to overwrite an existing file at the destination
path unless -OV(ERWRITE) is explicitly passed. This mirrors the
existing RECREATE vs RECREATE OVERWRITE guardrail on the restore side.

- Register IN_SW_BURP_OVERWRITE (58) with min prefix 2 (OV)
- Add gbl_sw_backup_overwrite flag to BurpGlobals
- Use O_EXCL (Unix) / CREATE_NEW (Windows) when opening backup files
  unless -OVERWRITE is specified
- Emit msg 424 on EEXIST/ERROR_FILE_EXISTS instead of generic msg 65
- Apply same treatment to split-backup volumes in mvol.cpp
@aafemt
Copy link
Contributor

aafemt commented Mar 14, 2026

May be for the sake of consistency it would be better to reuse -REP[LACE] option changing its description?.. Sure, its full form is REPLACE_DATABASE now but I honestly doubt that anyone ever used it in wild.

@kdmtctl
Copy link
Author

kdmtctl commented Mar 14, 2026

May be for the sake of consistency it would be better to reuse -REP[LACE] option changing its description?.. Sure, its full form is REPLACE_DATABASE now but I honestly doubt that anyone ever used it in wild.

So it the complete forced overwrite command should be gbak -b -replace gbak_test /tmp/existing.fbk in this case?

@kdmtctl
Copy link
Author

kdmtctl commented Mar 14, 2026

Tests. Didn't copy messages file though. But the error code is correct.

Test 5: gbak -b to existing file — should FAIL (msg 424)
Command:  touch /tmp/existing.fbk
          gbak -b gbak_test /tmp/existing.fbk -user SYSDBA -pass ***
Expected: rejected with msg 424
Result:   PASS
  gbak: ERROR:can't format message 12:424 -- message text not found
  gbak:Exiting before completion due to errors
  Exit code: 1

Test 6: gbak -b -ov to existing file — should SUCCEED
Command:  gbak -b -ov gbak_test /tmp/existing.fbk -user SYSDBA -pass ***
Expected: success (overwrite allowed)
Result:   PASS
  Exit code: 0
  -rw-r--r--. 1 root root 1024 /tmp/existing.fbk

Test 7: gbak -b to new file — should SUCCEED
Command:  gbak -b gbak_test /tmp/new_backup.fbk -user SYSDBA -pass ***
Expected: success (file does not exist, no O_EXCL conflict)
Result:   PASS
  Exit code: 0
  -rw-r--r--. 1 root root 1024 /tmp/new_backup.fbk

Test 8: gbak -b to stdout — should SUCCEED (O_EXCL not applied)
Command:  gbak -b gbak_test stdout -user SYSDBA -pass *** > /tmp/stdout_backup.fbk
Expected: success (stdout path bypasses file open)
Result:   PASS
  Exit code: 0
  -rw-r--r--. 1 sysop sysop 1024 /tmp/stdout_backup.fbk

Test 9: Split backup where part file exists — should FAIL (msg 424)
Command:  touch /tmp/split_b.fbk   # only second part exists
          gbak -b gbak_test /tmp/split_a.fbk 500k /tmp/split_b.fbk -user SYSDBA -pass ***
Expected: rejected with msg 424 (second file already exists)
Result:   PASS
  gbak: ERROR:can't format message 12:424 -- message text not found
  gbak:Exiting before completion due to errors
  Exit code: 1

Test 10: Split backup with -ov where part files exist — should SUCCEED
Command:  touch /tmp/split_a.fbk /tmp/split_b.fbk
          gbak -b -ov gbak_test /tmp/split_a.fbk 500k /tmp/split_b.fbk -user SYSDBA -pass ***
Expected: success (overwrite allowed)
Result:   PASS
  Exit code: 0
  -rw-r--r--. 1 root root 1124 /tmp/split_a.fbk
  -rw-r--r--. 1 root root  100 /tmp/split_b.fbk

Test 11: gbak -b duplicate destinations — should FAIL (msg 9)
Command:  gbak -b gbak_test /tmp/dup.fbk 500k /tmp/dup.fbk -user SYSDBA -pass ***
Expected: rejected with msg 9 (duplicate destination, existing check from first PR)
Result:   PASS
  gbak: ERROR:multiple sources or destinations specified
  gbak:Exiting before completion due to errors
  Exit code: 1

@kdmtctl
Copy link
Author

kdmtctl commented Mar 14, 2026

This is a CLI-only patch. I just glanced at how the Services API will call it. I noticed the parameters bitmask we would have to add to support the new one. Should we tackle this too?

@aafemt
Copy link
Contributor

aafemt commented Mar 14, 2026

So it the complete forced overwrite command should be gbak -b -replace gbak_test /tmp/existing.fbk in this case?

Yes, I think it would a better match for restore command gbak -c -replace file.bak /tmp/existing.fdb, but it is a matter of taste, of course.

Copy link
Member

@dyemanov dyemanov left a comment

Choose a reason for hiding this comment

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

Implementation is not consistent with what we had previously for RECREATE. It uses OVERWRITE as a optional command modifier:

-RECREATE [OVERWRITE] ...

and OVERWRITE is not expected outside the RECREATE command.
But the new code uses OVERWRITE as a regular top-level switch:

-BACKUP [-OVERWRITE] ...

I'm not going to argue what approach is better, but I'd rather respect the existing syntax for RECREATE and do the same for BACKUP. Just find out how BURP_SW_OVERWRITE is handled for recreate and do the same for backup.

src/burp/burp.h Outdated
bool gbl_sw_mode_val;
bool gbl_sw_overwrite;
bool gbl_sw_direct_io;
bool gbl_sw_backup_overwrite;
Copy link
Member

Choose a reason for hiding this comment

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

I'd better reuse the existing gbl_sw_overwrite option. Backup and restore are mutually exclusive commands.

@kdmtctl
Copy link
Author

kdmtctl commented Mar 15, 2026

I'm not going to argue what approach is better, but I'd rather respect the existing syntax for RECREATE and do the same for BACKUP. Just find out how BURP_SW_OVERWRITE is handled for recreate and do the same for backup.

On restore BURP_SW_OVERWRITE drops the destination DB if it exists. On backup we create files so mangling with flags.

I can do either. Just need a final decision from maintainers: replace or -recreate overwrite and reuse the flag. Please do argue.

Per review feedback, align backup's overwrite syntax with the existing
RECREATE [OVERWRITE] pattern on the restore side:

- Removed IN_SW_BURP_OVERWRITE switch and gbl_sw_backup_overwrite flag
- OVERWRITE is now a positional argument to -B(ACKUP_DATABASE),
  parsed the same way as RECREATE's optional OVERWRITE modifier
- Reuse existing BURP_SW_OVERWRITE string constant and gbl_sw_overwrite flag

Syntax:
  gbak -b <db> <file>              error if backup file already exists
  gbak -b OVERWRITE <db> <file>    allow overwriting existing backup file
@kdmtctl
Copy link
Author

kdmtctl commented Mar 15, 2026

@dyemanov You're right. OVERWRITE should be a positional argument. The new commit matches the existing RECREATE pattern, using the same gbl_sw_overwrite flag.

@aafemt -REPLACE is a standalone switch and there is some ambiguity, it is currently boMain and sets flag_restore. We either repurpose it for backup or make it work as a modifier requiring -b or -c as a first argument. Can do this but didn't plan to touch the restore logic.

Here is the full picture after the last commit:

Restore:

Switch = DB doesn't exist DB exists
-c IN_SW_BURP_C Create Error
-recreate IN_SW_BURP_C Create Error
-recreate OVERWRITE IN_SW_BURP_R Create Drop + Create
-rep IN_SW_BURP_R Create Drop + Create

Backup:

Switch = File doesn't exist File exists
-b default Create Error
-b OVERWRITE gbl_sw_overwrite Create Overwrite

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants