Coverage Report

Created: 2025-08-12 06:43

/src/postgres/src/backend/backup/basebackup_server.c
Line
Count
Source (jump to first uncovered line)
1
/*-------------------------------------------------------------------------
2
 *
3
 * basebackup_server.c
4
 *    store basebackup archives on the server
5
 *
6
 * IDENTIFICATION
7
 *    src/backend/backup/basebackup_server.c
8
 *
9
 *-------------------------------------------------------------------------
10
 */
11
#include "postgres.h"
12
13
#include "access/xact.h"
14
#include "backup/basebackup_sink.h"
15
#include "catalog/pg_authid.h"
16
#include "miscadmin.h"
17
#include "storage/fd.h"
18
#include "utils/acl.h"
19
#include "utils/wait_event.h"
20
21
typedef struct bbsink_server
22
{
23
  /* Common information for all types of sink. */
24
  bbsink    base;
25
26
  /* Directory in which backup is to be stored. */
27
  char     *pathname;
28
29
  /* Currently open file (or 0 if nothing open). */
30
  File    file;
31
32
  /* Current file position. */
33
  off_t   filepos;
34
} bbsink_server;
35
36
static void bbsink_server_begin_archive(bbsink *sink,
37
                    const char *archive_name);
38
static void bbsink_server_archive_contents(bbsink *sink, size_t len);
39
static void bbsink_server_end_archive(bbsink *sink);
40
static void bbsink_server_begin_manifest(bbsink *sink);
41
static void bbsink_server_manifest_contents(bbsink *sink, size_t len);
42
static void bbsink_server_end_manifest(bbsink *sink);
43
44
static const bbsink_ops bbsink_server_ops = {
45
  .begin_backup = bbsink_forward_begin_backup,
46
  .begin_archive = bbsink_server_begin_archive,
47
  .archive_contents = bbsink_server_archive_contents,
48
  .end_archive = bbsink_server_end_archive,
49
  .begin_manifest = bbsink_server_begin_manifest,
50
  .manifest_contents = bbsink_server_manifest_contents,
51
  .end_manifest = bbsink_server_end_manifest,
52
  .end_backup = bbsink_forward_end_backup,
53
  .cleanup = bbsink_forward_cleanup
54
};
55
56
/*
57
 * Create a new 'server' bbsink.
58
 */
59
bbsink *
60
bbsink_server_new(bbsink *next, char *pathname)
61
0
{
62
0
  bbsink_server *sink = palloc0(sizeof(bbsink_server));
63
64
0
  *((const bbsink_ops **) &sink->base.bbs_ops) = &bbsink_server_ops;
65
0
  sink->pathname = pathname;
66
0
  sink->base.bbs_next = next;
67
68
  /* Replication permission is not sufficient in this case. */
69
0
  StartTransactionCommand();
70
0
  if (!has_privs_of_role(GetUserId(), ROLE_PG_WRITE_SERVER_FILES))
71
0
    ereport(ERROR,
72
0
        (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
73
0
         errmsg("permission denied to create backup stored on server"),
74
0
         errdetail("Only roles with privileges of the \"%s\" role may create a backup stored on the server.",
75
0
               "pg_write_server_files")));
76
0
  CommitTransactionCommand();
77
78
  /*
79
   * It's not a good idea to store your backups in the same directory that
80
   * you're backing up. If we allowed a relative path here, that could
81
   * easily happen accidentally, so we don't. The user could still
82
   * accomplish the same thing by including the absolute path to $PGDATA in
83
   * the pathname, but that's likely an intentional bad decision rather than
84
   * an accident.
85
   */
86
0
  if (!is_absolute_path(pathname))
87
0
    ereport(ERROR,
88
0
        (errcode(ERRCODE_INVALID_NAME),
89
0
         errmsg("relative path not allowed for backup stored on server")));
90
91
0
  switch (pg_check_dir(pathname))
92
0
  {
93
0
    case 0:
94
95
      /*
96
       * Does not exist, so create it using the same permissions we'd
97
       * use for a new subdirectory of the data directory itself.
98
       */
99
0
      if (MakePGDirectory(pathname) < 0)
100
0
        ereport(ERROR,
101
0
            (errcode_for_file_access(),
102
0
             errmsg("could not create directory \"%s\": %m", pathname)));
103
0
      break;
104
105
0
    case 1:
106
      /* Exists, empty. */
107
0
      break;
108
109
0
    case 2:
110
0
    case 3:
111
0
    case 4:
112
      /* Exists, not empty. */
113
0
      ereport(ERROR,
114
0
          (errcode(ERRCODE_DUPLICATE_FILE),
115
0
           errmsg("directory \"%s\" exists but is not empty",
116
0
              pathname)));
117
0
      break;
118
119
0
    default:
120
      /* Access problem. */
121
0
      ereport(ERROR,
122
0
          (errcode_for_file_access(),
123
0
           errmsg("could not access directory \"%s\": %m",
124
0
              pathname)));
125
0
  }
126
127
0
  return &sink->base;
128
0
}
129
130
/*
131
 * Open the correct output file for this archive.
132
 */
133
static void
134
bbsink_server_begin_archive(bbsink *sink, const char *archive_name)
135
0
{
136
0
  bbsink_server *mysink = (bbsink_server *) sink;
137
0
  char     *filename;
138
139
0
  Assert(mysink->file == 0);
140
0
  Assert(mysink->filepos == 0);
141
142
0
  filename = psprintf("%s/%s", mysink->pathname, archive_name);
143
144
0
  mysink->file = PathNameOpenFile(filename,
145
0
                  O_CREAT | O_EXCL | O_WRONLY | PG_BINARY);
146
0
  if (mysink->file <= 0)
147
0
    ereport(ERROR,
148
0
        (errcode_for_file_access(),
149
0
         errmsg("could not create file \"%s\": %m", filename)));
150
151
0
  pfree(filename);
152
153
0
  bbsink_forward_begin_archive(sink, archive_name);
154
0
}
155
156
/*
157
 * Write the data to the output file.
158
 */
159
static void
160
bbsink_server_archive_contents(bbsink *sink, size_t len)
161
0
{
162
0
  bbsink_server *mysink = (bbsink_server *) sink;
163
0
  int     nbytes;
164
165
0
  nbytes = FileWrite(mysink->file, mysink->base.bbs_buffer, len,
166
0
             mysink->filepos, WAIT_EVENT_BASEBACKUP_WRITE);
167
168
0
  if (nbytes != len)
169
0
  {
170
0
    if (nbytes < 0)
171
0
      ereport(ERROR,
172
0
          (errcode_for_file_access(),
173
0
           errmsg("could not write file \"%s\": %m",
174
0
              FilePathName(mysink->file)),
175
0
           errhint("Check free disk space.")));
176
    /* short write: complain appropriately */
177
0
    ereport(ERROR,
178
0
        (errcode(ERRCODE_DISK_FULL),
179
0
         errmsg("could not write file \"%s\": wrote only %d of %d bytes at offset %u",
180
0
            FilePathName(mysink->file),
181
0
            nbytes, (int) len, (unsigned) mysink->filepos),
182
0
         errhint("Check free disk space.")));
183
0
  }
184
185
0
  mysink->filepos += nbytes;
186
187
0
  bbsink_forward_archive_contents(sink, len);
188
0
}
189
190
/*
191
 * fsync and close the current output file.
192
 */
193
static void
194
bbsink_server_end_archive(bbsink *sink)
195
0
{
196
0
  bbsink_server *mysink = (bbsink_server *) sink;
197
198
  /*
199
   * We intentionally don't use data_sync_elevel here, because the server
200
   * shouldn't PANIC just because we can't guarantee that the backup has
201
   * been written down to disk. Running recovery won't fix anything in this
202
   * case anyway.
203
   */
204
0
  if (FileSync(mysink->file, WAIT_EVENT_BASEBACKUP_SYNC) < 0)
205
0
    ereport(ERROR,
206
0
        (errcode_for_file_access(),
207
0
         errmsg("could not fsync file \"%s\": %m",
208
0
            FilePathName(mysink->file))));
209
210
211
  /* We're done with this file now. */
212
0
  FileClose(mysink->file);
213
0
  mysink->file = 0;
214
0
  mysink->filepos = 0;
215
216
0
  bbsink_forward_end_archive(sink);
217
0
}
218
219
/*
220
 * Open the output file to which we will write the manifest.
221
 *
222
 * Just like pg_basebackup, we write the manifest first under a temporary
223
 * name and then rename it into place after fsync. That way, if the manifest
224
 * is there and under the correct name, the user can be sure that the backup
225
 * completed.
226
 */
227
static void
228
bbsink_server_begin_manifest(bbsink *sink)
229
0
{
230
0
  bbsink_server *mysink = (bbsink_server *) sink;
231
0
  char     *tmp_filename;
232
233
0
  Assert(mysink->file == 0);
234
235
0
  tmp_filename = psprintf("%s/backup_manifest.tmp", mysink->pathname);
236
237
0
  mysink->file = PathNameOpenFile(tmp_filename,
238
0
                  O_CREAT | O_EXCL | O_WRONLY | PG_BINARY);
239
0
  if (mysink->file <= 0)
240
0
    ereport(ERROR,
241
0
        (errcode_for_file_access(),
242
0
         errmsg("could not create file \"%s\": %m", tmp_filename)));
243
244
0
  pfree(tmp_filename);
245
246
0
  bbsink_forward_begin_manifest(sink);
247
0
}
248
249
/*
250
 * Each chunk of manifest data is sent using a CopyData message.
251
 */
252
static void
253
bbsink_server_manifest_contents(bbsink *sink, size_t len)
254
0
{
255
0
  bbsink_server *mysink = (bbsink_server *) sink;
256
0
  int     nbytes;
257
258
0
  nbytes = FileWrite(mysink->file, mysink->base.bbs_buffer, len,
259
0
             mysink->filepos, WAIT_EVENT_BASEBACKUP_WRITE);
260
261
0
  if (nbytes != len)
262
0
  {
263
0
    if (nbytes < 0)
264
0
      ereport(ERROR,
265
0
          (errcode_for_file_access(),
266
0
           errmsg("could not write file \"%s\": %m",
267
0
              FilePathName(mysink->file)),
268
0
           errhint("Check free disk space.")));
269
    /* short write: complain appropriately */
270
0
    ereport(ERROR,
271
0
        (errcode(ERRCODE_DISK_FULL),
272
0
         errmsg("could not write file \"%s\": wrote only %d of %d bytes at offset %u",
273
0
            FilePathName(mysink->file),
274
0
            nbytes, (int) len, (unsigned) mysink->filepos),
275
0
         errhint("Check free disk space.")));
276
0
  }
277
278
0
  mysink->filepos += nbytes;
279
280
0
  bbsink_forward_manifest_contents(sink, len);
281
0
}
282
283
/*
284
 * fsync the backup manifest, close the file, and then rename it into place.
285
 */
286
static void
287
bbsink_server_end_manifest(bbsink *sink)
288
0
{
289
0
  bbsink_server *mysink = (bbsink_server *) sink;
290
0
  char     *tmp_filename;
291
0
  char     *filename;
292
293
  /* We're done with this file now. */
294
0
  FileClose(mysink->file);
295
0
  mysink->file = 0;
296
297
  /*
298
   * Rename it into place. This also fsyncs the temporary file, so we don't
299
   * need to do that here. We don't use data_sync_elevel here for the same
300
   * reasons as in bbsink_server_end_archive.
301
   */
302
0
  tmp_filename = psprintf("%s/backup_manifest.tmp", mysink->pathname);
303
0
  filename = psprintf("%s/backup_manifest", mysink->pathname);
304
0
  durable_rename(tmp_filename, filename, ERROR);
305
0
  pfree(filename);
306
0
  pfree(tmp_filename);
307
308
0
  bbsink_forward_end_manifest(sink);
309
0
}