Skip to content

Commit 6fd6c67

Browse files
authored
Merge pull request libgit2#4030 from libgit2/ethomson/fsync
fsync all the things
2 parents 7e53e8c + 1c04a96 commit 6fd6c67

28 files changed

+426
-44
lines changed

include/git2/common.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ typedef enum {
179179
GIT_OPT_SET_SSL_CIPHERS,
180180
GIT_OPT_GET_USER_AGENT,
181181
GIT_OPT_ENABLE_OFS_DELTA,
182+
GIT_OPT_ENABLE_SYNCHRONOUS_OBJECT_CREATION,
182183
} git_libgit2_opt_t;
183184

184185
/**
@@ -316,6 +317,13 @@ typedef enum {
316317
* > Packfiles containing offset deltas can still be read.
317318
* > This defaults to enabled.
318319
*
320+
* * opts(GIT_OPT_ENABLE_SYNCHRONOUS_OBJECT_CREATION, int enabled)
321+
*
322+
* > Enable synchronized writes of new objects using `fsync`
323+
* > (or the platform equivalent) to ensure that new object data
324+
* > is written to permanent storage, not simply cached. This
325+
* > defaults to disabled.
326+
*
319327
* @param option Option key
320328
* @param ... value to set the option
321329
* @return 0 on success, <0 on failure

include/git2/odb_backend.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ GIT_EXTERN(int) git_odb_backend_pack(git_odb_backend **out, const char *objects_
3939
* @param out location to store the odb backend pointer
4040
* @param objects_dir the Git repository's objects directory
4141
* @param compression_level zlib compression level to use
42-
* @param do_fsync whether to do an fsync() after writing (currently ignored)
42+
* @param do_fsync whether to do an fsync() after writing
4343
* @param dir_mode permissions to use creating a directory or 0 for defaults
4444
* @param file_mode permissions to use creating a file or 0 for defaults
4545
*

src/config_cache.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ static struct map_data _cvar_maps[] = {
7878
{"core.logallrefupdates", NULL, 0, GIT_LOGALLREFUPDATES_DEFAULT },
7979
{"core.protecthfs", NULL, 0, GIT_PROTECTHFS_DEFAULT },
8080
{"core.protectntfs", NULL, 0, GIT_PROTECTNTFS_DEFAULT },
81+
{"core.fsyncobjectfiles", NULL, 0, GIT_FSYNCOBJECTFILES_DEFAULT },
8182
};
8283

8384
int git_config__cvar(int *out, git_config *config, git_cvar_cached cvar)

src/filebuf.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,9 @@ int git_filebuf_open_withsize(git_filebuf *file, const char *path, int flags, mo
291291
if (flags & GIT_FILEBUF_DO_NOT_BUFFER)
292292
file->do_not_buffer = true;
293293

294+
if (flags & GIT_FILEBUF_FSYNC)
295+
file->do_fsync = true;
296+
294297
file->buf_size = size;
295298
file->buf_pos = 0;
296299
file->fd = -1;
@@ -425,6 +428,11 @@ int git_filebuf_commit(git_filebuf *file)
425428

426429
file->fd_is_open = false;
427430

431+
if (file->do_fsync && p_fsync(file->fd) < 0) {
432+
giterr_set(GITERR_OS, "failed to fsync '%s'", file->path_lock);
433+
goto on_error;
434+
}
435+
428436
if (p_close(file->fd) < 0) {
429437
giterr_set(GITERR_OS, "failed to close file at '%s'", file->path_lock);
430438
goto on_error;
@@ -437,6 +445,9 @@ int git_filebuf_commit(git_filebuf *file)
437445
goto on_error;
438446
}
439447

448+
if (file->do_fsync && git_futils_fsync_parent(file->path_original) < 0)
449+
goto on_error;
450+
440451
file->did_rename = true;
441452

442453
git_filebuf_cleanup(file);

src/filebuf.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@
2020
#define GIT_FILEBUF_FORCE (1 << 3)
2121
#define GIT_FILEBUF_TEMPORARY (1 << 4)
2222
#define GIT_FILEBUF_DO_NOT_BUFFER (1 << 5)
23-
#define GIT_FILEBUF_DEFLATE_SHIFT (6)
23+
#define GIT_FILEBUF_FSYNC (1 << 6)
24+
#define GIT_FILEBUF_DEFLATE_SHIFT (7)
2425

2526
#define GIT_FILELOCK_EXTENSION ".lock\0"
2627
#define GIT_FILELOCK_EXTLENGTH 6
@@ -47,6 +48,7 @@ struct git_filebuf {
4748
bool created_lock;
4849
bool did_rename;
4950
bool do_not_buffer;
51+
bool do_fsync;
5052
int last_error;
5153
};
5254

src/fileops.c

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -236,10 +236,16 @@ int git_futils_readbuffer(git_buf *buf, const char *path)
236236
int git_futils_writebuffer(
237237
const git_buf *buf, const char *path, int flags, mode_t mode)
238238
{
239-
int fd, error = 0;
239+
int fd, do_fsync = 0, error = 0;
240240

241-
if (flags <= 0)
241+
if (!flags)
242242
flags = O_CREAT | O_TRUNC | O_WRONLY;
243+
244+
if ((flags & O_FSYNC) != 0)
245+
do_fsync = 1;
246+
247+
flags &= ~O_FSYNC;
248+
243249
if (!mode)
244250
mode = GIT_FILEMODE_BLOB;
245251

@@ -254,8 +260,19 @@ int git_futils_writebuffer(
254260
return error;
255261
}
256262

257-
if ((error = p_close(fd)) < 0)
263+
if (do_fsync && (error = p_fsync(fd)) < 0) {
264+
giterr_set(GITERR_OS, "could not fsync '%s'", path);
265+
p_close(fd);
266+
return error;
267+
}
268+
269+
if ((error = p_close(fd)) < 0) {
258270
giterr_set(GITERR_OS, "error while closing '%s'", path);
271+
return error;
272+
}
273+
274+
if (do_fsync && (flags & O_CREAT))
275+
error = git_futils_fsync_parent(path);
259276

260277
return error;
261278
}
@@ -1108,3 +1125,33 @@ void git_futils_filestamp_set_from_stat(
11081125
memset(stamp, 0, sizeof(*stamp));
11091126
}
11101127
}
1128+
1129+
int git_futils_fsync_dir(const char *path)
1130+
{
1131+
#ifdef GIT_WIN32
1132+
GIT_UNUSED(path);
1133+
return 0;
1134+
#else
1135+
int fd, error = -1;
1136+
1137+
if ((fd = p_open(path, O_RDONLY)) < 0) {
1138+
giterr_set(GITERR_OS, "failed to open directory '%s' for fsync", path);
1139+
return -1;
1140+
}
1141+
1142+
if ((error = p_fsync(fd)) < 0)
1143+
giterr_set(GITERR_OS, "failed to fsync directory '%s'", path);
1144+
1145+
p_close(fd);
1146+
return error;
1147+
#endif
1148+
}
1149+
1150+
int git_futils_fsync_parent(const char *path)
1151+
{
1152+
char *parent = git_path_dirname(path);
1153+
int error = git_futils_fsync_dir(parent);
1154+
1155+
git__free(parent);
1156+
return error;
1157+
}

src/fileops.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,13 @@ extern int git_futils_readbuffer_updated(
2525
git_buf *obj, const char *path, git_oid *checksum, int *updated);
2626
extern int git_futils_readbuffer_fd(git_buf *obj, git_file fd, size_t len);
2727

28+
/* Additional constants for `git_futils_writebuffer`'s `open_flags`. We
29+
* support these internally and they will be removed before the `open` call.
30+
*/
31+
#ifndef O_FSYNC
32+
# define O_FSYNC (1 << 31)
33+
#endif
34+
2835
extern int git_futils_writebuffer(
2936
const git_buf *buf, const char *path, int open_flags, mode_t mode);
3037

@@ -356,4 +363,22 @@ extern void git_futils_filestamp_set(
356363
extern void git_futils_filestamp_set_from_stat(
357364
git_futils_filestamp *stamp, struct stat *st);
358365

366+
/**
367+
* `fsync` the parent directory of the given path, if `fsync` is
368+
* supported for directories on this platform.
369+
*
370+
* @param path Path of the directory to sync.
371+
* @return 0 on success, -1 on error
372+
*/
373+
extern int git_futils_fsync_dir(const char *path);
374+
375+
/**
376+
* `fsync` the parent directory of the given path, if `fsync` is
377+
* supported for directories on this platform.
378+
*
379+
* @param path Path of the file whose parent directory should be synced.
380+
* @return 0 on success, -1 on error
381+
*/
382+
extern int git_futils_fsync_parent(const char *path);
383+
359384
#endif /* INCLUDE_fileops_h__ */

src/indexer.c

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "oid.h"
1818
#include "oidmap.h"
1919
#include "zstream.h"
20+
#include "object.h"
2021

2122
extern git_mutex git__mwindow_mutex;
2223

@@ -33,7 +34,8 @@ struct git_indexer {
3334
unsigned int parsed_header :1,
3435
pack_committed :1,
3536
have_stream :1,
36-
have_delta :1;
37+
have_delta :1,
38+
do_fsync :1;
3739
struct git_pack_header hdr;
3840
struct git_pack_file *pack;
3941
unsigned int mode;
@@ -123,6 +125,9 @@ int git_indexer_new(
123125
git_hash_ctx_init(&idx->hash_ctx);
124126
git_hash_ctx_init(&idx->trailer);
125127

128+
if (git_object__synchronous_writing)
129+
idx->do_fsync = 1;
130+
126131
error = git_buf_joinpath(&path, prefix, suff);
127132
if (error < 0)
128133
goto cleanup;
@@ -161,6 +166,11 @@ int git_indexer_new(
161166
return -1;
162167
}
163168

169+
void git_indexer__set_fsync(git_indexer *idx, int do_fsync)
170+
{
171+
idx->do_fsync = !!do_fsync;
172+
}
173+
164174
/* Try to store the delta so we can try to resolve it later */
165175
static int store_delta(git_indexer *idx)
166176
{
@@ -989,7 +999,9 @@ int git_indexer_commit(git_indexer *idx, git_transfer_progress *stats)
989999
return -1;
9901000

9911001
if (git_filebuf_open(&index_file, filename.ptr,
992-
GIT_FILEBUF_HASH_CONTENTS, idx->mode) < 0)
1002+
GIT_FILEBUF_HASH_CONTENTS |
1003+
(idx->do_fsync ? GIT_FILEBUF_FSYNC : 0),
1004+
idx->mode) < 0)
9931005
goto on_error;
9941006

9951007
/* Write out the header */
@@ -1066,6 +1078,11 @@ int git_indexer_commit(git_indexer *idx, git_transfer_progress *stats)
10661078
return -1;
10671079
}
10681080

1081+
if (idx->do_fsync && p_fsync(idx->pack->mwf.fd) < 0) {
1082+
giterr_set(GITERR_OS, "failed to fsync packfile");
1083+
goto on_error;
1084+
}
1085+
10691086
/* We need to close the descriptor here so Windows doesn't choke on commit_at */
10701087
if (p_close(idx->pack->mwf.fd) < 0) {
10711088
giterr_set(GITERR_OS, "failed to close packfile");
@@ -1078,7 +1095,14 @@ int git_indexer_commit(git_indexer *idx, git_transfer_progress *stats)
10781095
goto on_error;
10791096

10801097
/* And don't forget to rename the packfile to its new place. */
1081-
p_rename(idx->pack->pack_name, git_buf_cstr(&filename));
1098+
if (p_rename(idx->pack->pack_name, git_buf_cstr(&filename)) < 0)
1099+
goto on_error;
1100+
1101+
/* And fsync the parent directory if we're asked to. */
1102+
if (idx->do_fsync &&
1103+
git_futils_fsync_parent(git_buf_cstr(&filename)) < 0)
1104+
goto on_error;
1105+
10821106
idx->pack_committed = 1;
10831107

10841108
git_buf_free(&filename);

src/indexer.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/*
2+
* Copyright (C) the libgit2 contributors. All rights reserved.
3+
*
4+
* This file is part of libgit2, distributed under the GNU GPL v2 with
5+
* a Linking Exception. For full terms see the included COPYING file.
6+
*/
7+
#ifndef INCLUDE_indexer_h__
8+
#define INCLUDE_indexer_h__
9+
10+
extern int git_indexer__set_fsync(git_indexer *idx, int do_fsync);
11+
12+
#endif

src/object.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "tag.h"
1717

1818
bool git_object__strict_input_validation = true;
19+
bool git_object__synchronous_writing = false;
1920

2021
typedef struct {
2122
const char *str; /* type name string */

0 commit comments

Comments
 (0)