Coverage Report

Created: 2026-10-17 07:15

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/git/tempfile.c
Line
Count
Source
1
/*
2
 * State diagram and cleanup
3
 * -------------------------
4
 *
5
 * If the program exits while a temporary file is active, we want to
6
 * make sure that we remove it. This is done by remembering the active
7
 * temporary files in a linked list, `tempfile_list`. An `atexit(3)`
8
 * handler and a signal handler are registered, to clean up any active
9
 * temporary files.
10
 *
11
 * Because the signal handler can run at any time, `tempfile_list` and
12
 * the `tempfile` objects that comprise it must be kept in
13
 * self-consistent states at all times.
14
 *
15
 * The possible states of a `tempfile` object are as follows:
16
 *
17
 * - Inactive/unallocated. The only way to get a tempfile is via a creation
18
 *   function like create_tempfile(). Once allocated, the tempfile is on the
19
 *   global tempfile_list and considered active.
20
 *
21
 * - Active, file open (after `create_tempfile()` or
22
 *   `reopen_tempfile()`). In this state:
23
 *
24
 *   - the temporary file exists
25
 *   - `filename` holds the filename of the temporary file
26
 *   - `fd` holds a file descriptor open for writing to it
27
 *   - `fp` holds a pointer to an open `FILE` object if and only if
28
 *     `fdopen_tempfile()` has been called on the object
29
 *   - `owner` holds the PID of the process that created the file
30
 *
31
 * - Active, file closed (after `close_tempfile_gently()`). Same
32
 *   as the previous state, except that the temporary file is closed,
33
 *   `fd` is -1, and `fp` is `NULL`.
34
 *
35
 * - Inactive (after `delete_tempfile()`, `rename_tempfile()`, or a
36
 *   failed attempt to create a temporary file). The struct is removed from
37
 *   the global tempfile_list and deallocated.
38
 *
39
 * A temporary file is owned by the process that created it. The
40
 * `tempfile` has an `owner` field that records the owner's PID. This
41
 * field is used to prevent a forked process from deleting a temporary
42
 * file created by its parent.
43
 */
44
45
#include "git-compat-util.h"
46
#include "abspath.h"
47
#include "path.h"
48
#include "tempfile.h"
49
#include "sigchain.h"
50
51
static VOLATILE_LIST_HEAD(tempfile_list);
52
53
static int remove_template_directory(struct tempfile *tempfile,
54
              int in_signal_handler)
55
0
{
56
0
  if (tempfile->directory) {
57
0
    if (in_signal_handler)
58
0
      return rmdir(tempfile->directory);
59
0
    else
60
0
      return rmdir_or_warn(tempfile->directory);
61
0
  }
62
63
0
  return 0;
64
0
}
65
66
static void remove_tempfiles(int in_signal_handler)
67
0
{
68
0
  pid_t me = getpid();
69
0
  volatile struct volatile_list_head *pos;
70
71
0
  list_for_each(pos, &tempfile_list) {
72
0
    struct tempfile *p = list_entry(pos, struct tempfile, list);
73
74
0
    if (!is_tempfile_active(p) || p->owner != me)
75
0
      continue;
76
77
0
    if (p->fd >= 0)
78
0
      close(p->fd);
79
80
0
    if (in_signal_handler)
81
0
      unlink(p->filename.buf);
82
0
    else
83
0
      unlink_or_warn(p->filename.buf);
84
0
    remove_template_directory(p, in_signal_handler);
85
0
  }
86
0
}
87
88
static void remove_tempfiles_on_exit(void)
89
0
{
90
0
  remove_tempfiles(0);
91
0
}
92
93
static void remove_tempfiles_on_signal(int signo)
94
0
{
95
0
  remove_tempfiles(1);
96
0
  sigchain_pop(signo);
97
0
  raise(signo);
98
0
}
99
100
static struct tempfile *new_tempfile(void)
101
0
{
102
0
  struct tempfile *tempfile = xmalloc(sizeof(*tempfile));
103
0
  tempfile->fd = -1;
104
0
  tempfile->fp = NULL;
105
0
  tempfile->owner = 0;
106
0
  INIT_LIST_HEAD(&tempfile->list);
107
0
  strbuf_init(&tempfile->filename, 0);
108
0
  tempfile->directory = NULL;
109
0
  return tempfile;
110
0
}
111
112
static void activate_tempfile(struct tempfile *tempfile)
113
0
{
114
0
  static int initialized;
115
116
0
  if (!initialized) {
117
0
    sigchain_push_common(remove_tempfiles_on_signal);
118
0
    atexit(remove_tempfiles_on_exit);
119
0
    initialized = 1;
120
0
  }
121
122
0
  volatile_list_add(&tempfile->list, &tempfile_list);
123
0
  tempfile->owner = getpid();
124
0
}
125
126
static void deactivate_tempfile(struct tempfile *tempfile)
127
0
{
128
0
  volatile_list_del(&tempfile->list);
129
0
  strbuf_release(&tempfile->filename);
130
0
  free(tempfile->directory);
131
0
  free(tempfile);
132
0
}
133
134
/* Make sure errno contains a meaningful value on error */
135
struct tempfile *repo_create_tempfile_mode(struct repository *r,
136
             const char *path, int mode)
137
0
{
138
0
  struct tempfile *tempfile = new_tempfile();
139
140
0
  strbuf_add_absolute_path(&tempfile->filename, path);
141
0
  tempfile->fd = open(tempfile->filename.buf,
142
0
          O_RDWR | O_CREAT | O_EXCL | O_CLOEXEC, mode);
143
0
  if (O_CLOEXEC && tempfile->fd < 0 && errno == EINVAL)
144
    /* Try again w/o O_CLOEXEC: the kernel might not support it */
145
0
    tempfile->fd = open(tempfile->filename.buf,
146
0
            O_RDWR | O_CREAT | O_EXCL, mode);
147
0
  if (tempfile->fd < 0) {
148
0
    deactivate_tempfile(tempfile);
149
0
    return NULL;
150
0
  }
151
0
  activate_tempfile(tempfile);
152
0
  if (adjust_shared_perm(r, tempfile->filename.buf)) {
153
0
    int save_errno = errno;
154
0
    error("cannot fix permission bits on %s", tempfile->filename.buf);
155
0
    delete_tempfile(&tempfile);
156
0
    errno = save_errno;
157
0
    return NULL;
158
0
  }
159
160
0
  return tempfile;
161
0
}
162
163
struct tempfile *register_tempfile(const char *path)
164
0
{
165
0
  struct tempfile *tempfile = new_tempfile();
166
0
  strbuf_add_absolute_path(&tempfile->filename, path);
167
0
  activate_tempfile(tempfile);
168
0
  return tempfile;
169
0
}
170
171
struct tempfile *mks_tempfile_sm(const char *filename_template, int suffixlen, int mode)
172
0
{
173
0
  struct tempfile *tempfile = new_tempfile();
174
175
0
  strbuf_add_absolute_path(&tempfile->filename, filename_template);
176
0
  tempfile->fd = git_mkstemps_mode(tempfile->filename.buf, suffixlen, mode);
177
0
  if (tempfile->fd < 0) {
178
0
    deactivate_tempfile(tempfile);
179
0
    return NULL;
180
0
  }
181
0
  activate_tempfile(tempfile);
182
0
  return tempfile;
183
0
}
184
185
struct tempfile *mks_tempfile_tsm(const char *filename_template, int suffixlen, int mode)
186
0
{
187
0
  struct tempfile *tempfile = new_tempfile();
188
0
  const char *tmpdir;
189
190
0
  tmpdir = getenv("TMPDIR");
191
0
  if (!tmpdir)
192
0
    tmpdir = "/tmp";
193
194
0
  strbuf_addf(&tempfile->filename, "%s/%s", tmpdir, filename_template);
195
0
  tempfile->fd = git_mkstemps_mode(tempfile->filename.buf, suffixlen, mode);
196
0
  if (tempfile->fd < 0) {
197
0
    deactivate_tempfile(tempfile);
198
0
    return NULL;
199
0
  }
200
0
  activate_tempfile(tempfile);
201
0
  return tempfile;
202
0
}
203
204
struct tempfile *mks_tempfile_dt(const char *directory_template,
205
         const char *filename)
206
0
{
207
0
  struct tempfile *tempfile;
208
0
  const char *tmpdir;
209
0
  struct strbuf sb = STRBUF_INIT;
210
0
  int fd;
211
0
  size_t directorylen;
212
213
0
  if (!ends_with(directory_template, "XXXXXX")) {
214
0
    errno = EINVAL;
215
0
    return NULL;
216
0
  }
217
218
0
  tmpdir = getenv("TMPDIR");
219
0
  if (!tmpdir)
220
0
    tmpdir = "/tmp";
221
222
0
  strbuf_addf(&sb, "%s/%s", tmpdir, directory_template);
223
0
  directorylen = sb.len;
224
0
  if (!mkdtemp(sb.buf)) {
225
0
    int orig_errno = errno;
226
0
    strbuf_release(&sb);
227
0
    errno = orig_errno;
228
0
    return NULL;
229
0
  }
230
231
0
  strbuf_addf(&sb, "/%s", filename);
232
0
  fd = open(sb.buf, O_CREAT | O_EXCL | O_RDWR, 0600);
233
0
  if (fd < 0) {
234
0
    int orig_errno = errno;
235
0
    strbuf_setlen(&sb, directorylen);
236
0
    rmdir(sb.buf);
237
0
    strbuf_release(&sb);
238
0
    errno = orig_errno;
239
0
    return NULL;
240
0
  }
241
242
0
  tempfile = new_tempfile();
243
0
  strbuf_swap(&tempfile->filename, &sb);
244
0
  tempfile->directory = xmemdupz(tempfile->filename.buf, directorylen);
245
0
  tempfile->fd = fd;
246
0
  activate_tempfile(tempfile);
247
0
  return tempfile;
248
0
}
249
250
struct tempfile *xmks_tempfile_m(const char *filename_template, int mode)
251
0
{
252
0
  struct tempfile *tempfile;
253
0
  struct strbuf full_template = STRBUF_INIT;
254
255
0
  strbuf_add_absolute_path(&full_template, filename_template);
256
0
  tempfile = mks_tempfile_m(full_template.buf, mode);
257
0
  if (!tempfile)
258
0
    die_errno("Unable to create temporary file '%s'",
259
0
        full_template.buf);
260
261
0
  strbuf_release(&full_template);
262
0
  return tempfile;
263
0
}
264
265
FILE *fdopen_tempfile(struct tempfile *tempfile, const char *mode)
266
0
{
267
0
  if (!is_tempfile_active(tempfile))
268
0
    BUG("fdopen_tempfile() called for inactive object");
269
0
  if (tempfile->fp)
270
0
    BUG("fdopen_tempfile() called for open object");
271
272
0
  tempfile->fp = fdopen(tempfile->fd, mode);
273
0
  return tempfile->fp;
274
0
}
275
276
const char *get_tempfile_path(struct tempfile *tempfile)
277
0
{
278
0
  if (!is_tempfile_active(tempfile))
279
0
    BUG("get_tempfile_path() called for inactive object");
280
0
  return tempfile->filename.buf;
281
0
}
282
283
int get_tempfile_fd(struct tempfile *tempfile)
284
0
{
285
0
  if (!is_tempfile_active(tempfile))
286
0
    BUG("get_tempfile_fd() called for inactive object");
287
0
  return tempfile->fd;
288
0
}
289
290
FILE *get_tempfile_fp(struct tempfile *tempfile)
291
0
{
292
0
  if (!is_tempfile_active(tempfile))
293
0
    BUG("get_tempfile_fp() called for inactive object");
294
0
  return tempfile->fp;
295
0
}
296
297
int close_tempfile_gently(struct tempfile *tempfile)
298
0
{
299
0
  int fd;
300
0
  FILE *fp;
301
0
  int err;
302
303
0
  if (!is_tempfile_active(tempfile) || tempfile->fd < 0)
304
0
    return 0;
305
306
0
  fd = tempfile->fd;
307
0
  fp = tempfile->fp;
308
0
  tempfile->fd = -1;
309
0
  if (fp) {
310
0
    tempfile->fp = NULL;
311
0
    if (ferror(fp)) {
312
0
      err = -1;
313
0
      if (!fclose(fp))
314
0
        errno = EIO;
315
0
    } else {
316
0
      err = fclose(fp);
317
0
    }
318
0
  } else {
319
0
    err = close(fd);
320
0
  }
321
322
0
  return err ? -1 : 0;
323
0
}
324
325
int reopen_tempfile(struct tempfile *tempfile)
326
0
{
327
0
  if (!is_tempfile_active(tempfile))
328
0
    BUG("reopen_tempfile called for an inactive object");
329
0
  if (0 <= tempfile->fd)
330
0
    BUG("reopen_tempfile called for an open object");
331
0
  tempfile->fd = open(tempfile->filename.buf, O_WRONLY|O_TRUNC);
332
0
  return tempfile->fd;
333
0
}
334
335
int rename_tempfile(struct tempfile **tempfile_p, const char *path)
336
0
{
337
0
  struct tempfile *tempfile = *tempfile_p;
338
339
0
  if (!is_tempfile_active(tempfile))
340
0
    BUG("rename_tempfile called for inactive object");
341
342
0
  if (close_tempfile_gently(tempfile)) {
343
0
    delete_tempfile(tempfile_p);
344
0
    return -1;
345
0
  }
346
347
0
  if (rename(tempfile->filename.buf, path)) {
348
0
    int save_errno = errno;
349
0
    delete_tempfile(tempfile_p);
350
0
    errno = save_errno;
351
0
    return -1;
352
0
  }
353
354
0
  deactivate_tempfile(tempfile);
355
0
  *tempfile_p = NULL;
356
0
  return 0;
357
0
}
358
359
int delete_tempfile(struct tempfile **tempfile_p)
360
0
{
361
0
  struct tempfile *tempfile = *tempfile_p;
362
0
  int err = 0;
363
364
0
  if (!is_tempfile_active(tempfile))
365
0
    return 0;
366
367
0
  err |= close_tempfile_gently(tempfile);
368
0
  err |= unlink_or_warn(tempfile->filename.buf);
369
0
  err |= remove_template_directory(tempfile, 0);
370
0
  deactivate_tempfile(tempfile);
371
0
  *tempfile_p = NULL;
372
373
0
  return err ? -1 : 0;
374
0
}
375
376
void reassign_tempfile_ownership(pid_t from, pid_t to)
377
0
{
378
0
  volatile struct volatile_list_head *pos;
379
380
0
  list_for_each(pos, &tempfile_list) {
381
0
    struct tempfile *p = list_entry(pos, struct tempfile, list);
382
383
0
    if (is_tempfile_active(p) && p->owner == from)
384
0
      p->owner = to;
385
0
  }
386
0
}