diff options
-rw-r--r-- | src/posix.c | 38 | ||||
-rw-r--r-- | src/posix.h | 1 | ||||
-rw-r--r-- | src/win32/posix_w32.c | 52 | ||||
-rw-r--r-- | tests/core/posix.c | 41 | ||||
-rw-r--r-- | tests/object/tree/read.c | 49 |
5 files changed, 181 insertions, 0 deletions
diff --git a/src/posix.c b/src/posix.c index bffe02e05..b5dfac56d 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 2934f2479..0119b6bb7 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 d8bbebb5d..fcaf77e89 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 172462073..529ee37f7 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 a0eae11d1..1ba058fc0 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); +} |