Commit aeea1c463941806a0176859c06c0e2e9716efcb0

Edward Thomson 2019-04-04T15:06:44

Merge pull request #4874 from tiennou/test/4615 Test that largefiles can be read through the tree API

diff --git a/src/posix.c b/src/posix.c
index bffe02e..b5dfac5 100644
--- a/src/posix.c
+++ b/src/posix.c
@@ -155,6 +155,44 @@ int p_rename(const char *from, const char *to)
 	return -1;
 }
 
+int p_fallocate(int fd, off_t offset, off_t len)
+{
+#ifdef __APPLE__
+	fstore_t prealloc;
+	struct stat st;
+	size_t newsize;
+	int error;
+
+	if ((error = p_fstat(fd, &st)) < 0)
+		return error;
+
+	if (git__add_sizet_overflow(&newsize, offset, len)) {
+		errno = EINVAL;
+		return -1;
+	}
+
+	if (newsize < (unsigned long long)st.st_size)
+		return 0;
+
+	memset(&prealloc, 0, sizeof(prealloc));
+	prealloc.fst_flags  = F_ALLOCATEALL;
+	prealloc.fst_posmode = F_PEOFPOSMODE;
+	prealloc.fst_offset = offset;
+	prealloc.fst_length = len;
+
+	/*
+	 * fcntl will often error when the file already exists; ignore
+	 * this error since ftruncate will also resize the file (although
+	 * likely slower).
+	 */
+	fcntl(fd, F_PREALLOCATE, &prealloc);
+
+	return ftruncate(fd, (offset + len));
+#else
+	return posix_fallocate(fd, offset, len);
+#endif
+}
+
 #endif /* GIT_WIN32 */
 
 ssize_t p_read(git_file fd, void *buf, size_t cnt)
diff --git a/src/posix.h b/src/posix.h
index 2934f24..0119b6b 100644
--- a/src/posix.h
+++ b/src/posix.h
@@ -115,6 +115,7 @@ extern int p_open(const char *path, int flags, ...);
 extern int p_creat(const char *path, mode_t mode);
 extern int p_getcwd(char *buffer_out, size_t size);
 extern int p_rename(const char *from, const char *to);
+extern int p_fallocate(int fd, off_t offset, off_t len);
 
 extern int git__page_size(size_t *page_size);
 extern int git__mmap_alignment(size_t *page_size);
diff --git a/src/win32/posix_w32.c b/src/win32/posix_w32.c
index d8bbebb..fcaf77e 100644
--- a/src/win32/posix_w32.c
+++ b/src/win32/posix_w32.c
@@ -516,6 +516,58 @@ int p_creat(const char *path, mode_t mode)
 	return p_open(path, O_WRONLY | O_CREAT | O_TRUNC, mode);
 }
 
+int p_fallocate(int fd, off_t offset, off_t len)
+{
+	HANDLE fh = (HANDLE)_get_osfhandle(fd);
+	LARGE_INTEGER zero, position, oldsize, newsize;
+	size_t size;
+
+	if (fh == INVALID_HANDLE_VALUE) {
+		errno = EBADF;
+		return -1;
+	}
+
+	if (offset < 0 || len <= 0) {
+		errno = EINVAL;
+		return -1;
+	}
+
+	if (git__add_sizet_overflow(&size, offset, len)) {
+		errno = EINVAL;
+		return -1;
+	}
+
+	zero.u.LowPart = 0;
+	zero.u.HighPart = 0;
+
+	newsize.u.LowPart = (size & 0xffffffff);
+
+#if (SIZE_MAX > UINT32_MAX)
+	newsize.u.HighPart = size >> 32;
+#else
+	newsize.u.HighPart = 0;
+#endif
+
+	if (!GetFileSizeEx(fh, &oldsize)) {
+		set_errno();
+		return -1;
+	}
+
+	/* POSIX emulation: attempting to shrink the file is ignored */
+	if (oldsize.QuadPart >= newsize.QuadPart)
+		return 0;
+
+	if (!SetFilePointerEx(fh, zero, &position, FILE_CURRENT) ||
+	    !SetFilePointerEx(fh, newsize, NULL, FILE_BEGIN) ||
+	    !SetEndOfFile(fh) ||
+	    !SetFilePointerEx(fh, position, 0, FILE_BEGIN)) {
+		set_errno();
+		return -1;
+	}
+
+	return 0;
+}
+
 int p_utimes(const char *path, const struct p_timeval times[2])
 {
 	git_win32_path wpath;
diff --git a/tests/core/posix.c b/tests/core/posix.c
index 1724620..529ee37 100644
--- a/tests/core/posix.c
+++ b/tests/core/posix.c
@@ -27,6 +27,11 @@ void test_core_posix__initialize(void)
 #endif
 }
 
+void test_core_posix__cleanup(void)
+{
+    p_unlink("fallocate_test");
+}
+
 static bool supports_ipv6(void)
 {
 #ifdef GIT_WIN32
@@ -190,3 +195,39 @@ void test_core_posix__p_regcomp_compile_userdiff_regexps(void)
 		cl_must_pass(error);
 	}
 }
+
+void test_core_posix__fallocate(void)
+{
+	int fd;
+	struct stat st;
+
+	/* fallocate a new file succeeds */
+	cl_must_pass(fd = p_open("fallocate_test", O_RDWR|O_CREAT, 0666));
+	cl_must_pass(p_fallocate(fd, 0, 42));
+	cl_must_pass(p_fstat(fd, &st));
+	cl_assert_equal_i(42, st.st_size);
+	p_close(fd);
+
+	/* fallocate an existing file succeeds */
+	cl_must_pass(fd = p_open("fallocate_test", O_RDWR, 0666));
+	cl_must_pass(p_fallocate(fd, 90, 9));
+	cl_must_pass(p_fstat(fd, &st));
+	cl_assert_equal_i(99, st.st_size);
+	p_close(fd);
+
+	/* fallocate doesn't shrink */
+	cl_must_pass(fd = p_open("fallocate_test", O_RDWR, 0666));
+	cl_must_pass(p_fallocate(fd, 0, 14));
+	cl_must_pass(p_fstat(fd, &st));
+	cl_assert_equal_i(99, st.st_size);
+	p_close(fd);
+
+	/* fallocate doesn't move the cursor */
+	cl_must_pass(fd = p_open("fallocate_test", O_RDWR, 0666));
+	cl_must_pass(p_fallocate(fd, 0, 100));
+	cl_assert_equal_i(0, p_lseek(fd, 0, SEEK_CUR));
+	cl_must_pass(p_lseek(fd, 42, SEEK_SET));
+	cl_must_pass(p_fallocate(fd, 0, 200));
+	cl_assert_equal_i(42, p_lseek(fd, 0, SEEK_CUR));
+	p_close(fd);
+}
diff --git a/tests/object/tree/read.c b/tests/object/tree/read.c
index a0eae11..1ba058f 100644
--- a/tests/object/tree/read.c
+++ b/tests/object/tree/read.c
@@ -73,3 +73,52 @@ void test_object_tree_read__two(void)
 	git_object_free(obj);
 	git_tree_free(tree);
 }
+
+#define BIGFILE "bigfile"
+#define BIGFILE_SIZE (off_t)4 * 1024 * 1024 * 1024 /* 4 GiB */
+
+void test_object_tree_read__largefile(void)
+{
+	git_reference *ref;
+	git_commit *commit;
+	git_tree *tree;
+	git_oid oid;
+	const git_tree_entry *entry;
+	git_object *object;
+	git_buf file = GIT_BUF_INIT;
+	int fd;
+	git_index *idx;
+
+	if (!cl_is_env_set("GITTEST_INVASIVE_FS_SIZE"))
+		cl_skip();
+
+	cl_git_pass(git_reference_lookup(&ref, g_repo, "refs/heads/master"));
+	cl_git_pass(git_repository_index(&idx, g_repo));
+
+	cl_git_pass(git_buf_puts(&file, git_repository_workdir(g_repo)));
+	cl_git_pass(git_buf_joinpath(&file, file.ptr, BIGFILE));
+
+	fd = p_open(git_buf_cstr(&file), O_CREAT|O_RDWR, 0644);
+	cl_assert_(fd >= 0, "invalid file descriptor");
+
+	cl_must_pass(p_fallocate(fd, 0, BIGFILE_SIZE));
+	cl_must_pass(p_close(fd));
+
+	cl_git_pass(git_index_add_bypath(idx, BIGFILE));
+	cl_repo_commit_from_index(&oid, g_repo, NULL, 0, "bigfile");
+
+	cl_git_pass(git_commit_lookup(&commit, g_repo, &oid));
+	cl_git_pass(git_commit_tree(&tree, commit));
+
+	entry = git_tree_entry_byname(tree, BIGFILE);
+	cl_assert_(entry, "entry was NULL");
+
+	cl_git_pass(git_tree_entry_to_object(&object, g_repo, entry));
+
+	git_buf_dispose(&file);
+	git_object_free(object);
+	git_tree_free(tree);
+	git_index_free(idx);
+	git_commit_free(commit);
+	git_reference_free(ref);
+}