# 58P02 — duplicate_file

> Source-backed reference for PostgreSQL SQLSTATE 58P02.
---

# 58P02

## At a glance {#at-a-glance}

`58P02` is the duplicate-file condition in the fixed server-side base-backup output path. The path accepts a missing directory, accepts an existing empty directory, and raises this code when the existing target is non-empty.

<!-- BEGIN SQLSTATE FACTS: generated by scripts/generate.py; do not edit -->

| Field | Value |
| --- | --- |
| SQLSTATE | `58P02` |
| Condition | `duplicate_file` |
| Status | `active` |
| Known present by | `7.4` |
| Locked snapshots | `9.0.23, 9.1.24, 9.2.24, 9.3.25, 9.4.26, 9.5.25, 9.6.24, 10.23, 11.22, 12.22, 13.23, 14.24, 15.19, 16.15, 17.11, 18.6, 19beta3` |
| Macros | `ERRCODE_DUPLICATE_FILE` |
| Aliases | `—` |

<!-- source facts: data/errcodes/58P02.json -->
<!-- END SQLSTATE FACTS -->

## Meaning {#meaning}

`bbsink_server_new()` first performs and commits its privilege check transaction. It then rejects relative paths and calls `pg_check_dir(pathname)`: result 0 creates the directory, result 1 accepts an empty directory, and results 2–4 enter the fixed `ERRCODE_DUPLICATE_FILE` branch. An access error takes a separate `errcode_for_file_access()` branch. This is a base-backup output safety check, not proof that every `EEXIST` or non-empty directory in PostgreSQL uses `58P02`.

## Messages {#messages}

- **ERROR**, SQLSTATE `58P02`: `directory "%s" exists but is not empty`.
- **ERROR**, with `errcode_for_file_access()`: `could not access directory "%s": %m`.
- **ERROR**, with `errcode_for_file_access()`: `could not create directory "%s": %m` when the target is absent but creation fails.

The last two templates receive their SQLSTATE from the saved OS errno and should not be relabeled `58P02` merely because they occur in the same function.

## Diagnosis {#diagnosis}

Confirm that the path is the intended base-backup destination, inspect its owner, mount, and inventory, and determine whether it contains a prior complete backup or an interrupted one. Preserve that inventory before changing it. The source path performs output preparation after the internal privilege-check transaction has been committed, so this condition is a failed backup setup rather than evidence that a user DML transaction can be repaired by blindly continuing.

## Response {#response}

Use a documented empty destination or archive the previous output according to the backup policy. Validate the completed backup and its manifest after creation; never recursively delete or force-overwrite an unknown directory. If an outer client command reports an `ERROR`, follow the actual session state and roll back to an existing savepoint or transaction as appropriate, but do not treat `ROLLBACK` as a way to make the non-empty backup target safe. A new connection is needed only if the owning server process was terminated.

## Versions {#versions}

The locked catalogue records this condition from 7.4; the fixed base-backup path is from PostgreSQL 18.6. No backup target was altered or tested for this source-only entry.

## Related {#related}

[`58P01`](../58p01/), [`58P03`](../58p03/), [`53100`](../53100/)

## Sources {#sources}

[`src/backend/backup/basebackup_server.c#L59-L125`](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/src/backend/backup/basebackup_server.c#L59)

The structured [evidence record](../data/evidence/58p02.json) records the directory-state branches, dynamic errno paths, and source/runtime boundary.
