Skip to content

Commit 6571ba7

Browse files
committed
Only avoid mmap(2)/ftruncate(2) when in non-Windows
It turns out that if we use `mmap(2)`, non-Windows remote filesystems break due to permissions. If we don't, _Windows_ remote filesystems break due to lack of coherence between memory mapped views of the file and direct I/O operations done to the files. To break out of this impossible situation, conditionally-compile versions of Windows-specific `write_at` and `append_to_pack`.
1 parent eeceaac commit 6571ba7

File tree

1 file changed

+84
-2
lines changed

1 file changed

+84
-2
lines changed

src/indexer.c

Lines changed: 84 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -601,6 +601,8 @@ static void hash_partially(git_indexer *idx, const uint8_t *data, size_t size)
601601
idx->inbuf_len += size - to_expell;
602602
}
603603

604+
#if defined(NO_MMAP) || !defined(GIT_WIN32)
605+
604606
static int write_at(git_indexer *idx, const void *data, off64_t offset, size_t size)
605607
{
606608
size_t remaining_size = size;
@@ -624,17 +626,86 @@ static int write_at(git_indexer *idx, const void *data, off64_t offset, size_t s
624626

625627
static int append_to_pack(git_indexer *idx, const void *data, size_t size)
626628
{
629+
if (write_at(idx, data, idx->pack->mwf.size, size) < 0) {
630+
git_error_set(GIT_ERROR_OS, "cannot extend packfile '%s'", idx->pack->pack_name);
631+
return -1;
632+
}
633+
634+
return 0;
635+
}
636+
637+
#else
638+
639+
/*
640+
* Windows may keep different views to a networked file for the mmap- and
641+
* open-accessed versions of a file, so any writes done through
642+
* `write(2)`/`pwrite(2)` may not be reflected on the data that `mmap(2)` is
643+
* able to read.
644+
*/
645+
646+
static int write_at(git_indexer *idx, const void *data, off64_t offset, size_t size)
647+
{
648+
git_file fd = idx->pack->mwf.fd;
649+
size_t mmap_alignment;
650+
size_t page_offset;
651+
off64_t page_start;
652+
unsigned char *map_data;
653+
git_map map;
654+
int error;
655+
656+
GIT_ASSERT_ARG(data);
657+
GIT_ASSERT_ARG(size);
658+
659+
if ((error = git__mmap_alignment(&mmap_alignment)) < 0)
660+
return error;
661+
662+
/* the offset needs to be at the mmap boundary for the platform */
663+
page_offset = offset % mmap_alignment;
664+
page_start = offset - page_offset;
665+
666+
if ((error = p_mmap(&map, page_offset + size, GIT_PROT_WRITE, GIT_MAP_SHARED, fd, page_start)) < 0)
667+
return error;
668+
669+
map_data = (unsigned char *)map.data;
670+
memcpy(map_data + page_offset, data, size);
671+
p_munmap(&map);
672+
673+
return 0;
674+
}
675+
676+
static int append_to_pack(git_indexer *idx, const void *data, size_t size)
677+
{
678+
off64_t new_size;
679+
size_t mmap_alignment;
680+
size_t page_offset;
681+
off64_t page_start;
682+
off64_t current_size = idx->pack->mwf.size;
683+
int error;
684+
627685
if (!size)
628686
return 0;
629687

630-
if (write_at(idx, data, idx->pack->mwf.size, size) < 0) {
688+
if ((error = git__mmap_alignment(&mmap_alignment)) < 0)
689+
return error;
690+
691+
/* Write a single byte to force the file system to allocate space now or
692+
* report an error, since we can't report errors when writing using mmap.
693+
* Round the size up to the nearest page so that we only need to perform file
694+
* I/O when we add a page, instead of whenever we write even a single byte. */
695+
new_size = current_size + size;
696+
page_offset = new_size % mmap_alignment;
697+
page_start = new_size - page_offset;
698+
699+
if (p_pwrite(idx->pack->mwf.fd, data, 1, page_start + mmap_alignment - 1) < 0) {
631700
git_error_set(GIT_ERROR_OS, "cannot extend packfile '%s'", idx->pack->pack_name);
632701
return -1;
633702
}
634703

635-
return 0;
704+
return write_at(idx, data, idx->pack->mwf.size, size);
636705
}
637706

707+
#endif
708+
638709
static int read_stream_object(git_indexer *idx, git_indexer_progress *stats)
639710
{
640711
git_packfile_stream *stream = &idx->stream;
@@ -1234,6 +1305,17 @@ int git_indexer_commit(git_indexer *idx, git_indexer_progress *stats)
12341305
if (git_mwindow_free_all(&idx->pack->mwf) < 0)
12351306
goto on_error;
12361307

1308+
#if !defined(NO_MMAP) && defined(GIT_WIN32)
1309+
/*
1310+
* Truncate file to undo rounding up to next page_size in append_to_pack only
1311+
* when mmap was used, to prevent failures in non-Windows remote filesystems.
1312+
*/
1313+
if (p_ftruncate(idx->pack->mwf.fd, idx->pack->mwf.size) < 0) {
1314+
git_error_set(GIT_ERROR_OS, "failed to truncate pack file '%s'", idx->pack->pack_name);
1315+
return -1;
1316+
}
1317+
#endif
1318+
12371319
if (idx->do_fsync && p_fsync(idx->pack->mwf.fd) < 0) {
12381320
git_error_set(GIT_ERROR_OS, "failed to fsync packfile");
12391321
goto on_error;

0 commit comments

Comments
 (0)