Commit 7e9b21aa2a13b97b54b0083eb8f40b7f428bc829

Jeff Hostetler 2015-02-27T08:54:48

Fix p_ftruncate to handle big files for git_clone

diff --git a/src/win32/posix.h b/src/win32/posix.h
index 104966e..9ac7843 100644
--- a/src/win32/posix.h
+++ b/src/win32/posix.h
@@ -41,7 +41,7 @@ extern int p_chdir(const char* path);
 extern int p_chmod(const char* path, mode_t mode);
 extern int p_rmdir(const char* path);
 extern int p_access(const char* path, mode_t mode);
-extern int p_ftruncate(int fd, long size);
+extern int p_ftruncate(int fd, git_off_t size);
 
 /* p_lstat is almost but not quite POSIX correct.  Specifically, the use of
  * ENOTDIR is wrong, in that it does not mean precisely that a non-directory
diff --git a/src/win32/posix_w32.c b/src/win32/posix_w32.c
index 6e005c1..874cba9 100644
--- a/src/win32/posix_w32.c
+++ b/src/win32/posix_w32.c
@@ -42,12 +42,30 @@
 /* GetFinalPathNameByHandleW signature */
 typedef DWORD(WINAPI *PFGetFinalPathNameByHandleW)(HANDLE, LPWSTR, DWORD, DWORD);
 
-int p_ftruncate(int fd, long size)
+/**
+ * Truncate or extend file.
+ *
+ * We now take a "git_off_t" rather than "long" because
+ * files may be longer than 2Gb.
+ */
+int p_ftruncate(int fd, git_off_t size)
 {
+	if (size < 0) {
+		errno = EINVAL;
+		return -1;
+	}
+
 #if defined(_MSC_VER) && _MSC_VER >= 1500
-	return _chsize_s(fd, size);
+	return ((_chsize_s(fd, size) == 0) ? 0 : -1);
 #else
-	return _chsize(fd, size);
+	/* TODO Find a replacement for _chsize() that handles big files.
+	 *      This comment is probably __MINGW32__ specific.
+	 */
+	if (size > INT32_MAX) {
+		errno = EFBIG;
+		return -1;
+	}
+	return _chsize(fd, (long)size);
 #endif
 }
 
diff --git a/tests/core/ftruncate.c b/tests/core/ftruncate.c
new file mode 100644
index 0000000..21981d6
--- /dev/null
+++ b/tests/core/ftruncate.c
@@ -0,0 +1,48 @@
+/**
+ * Some tests for p_ftruncate() to ensure that
+ * properly handles large (2Gb+) files.
+ */
+
+#include "clar_libgit2.h"
+
+static const char *filename = "core_ftruncate.txt";
+static int fd = -1;
+
+void test_core_ftruncate__initialize(void)
+{
+	if (!cl_getenv("GITTEST_INVASIVE_FS_SIZE"))
+		cl_skip();
+
+	cl_must_pass((fd = p_open(filename, O_CREAT | O_RDWR, 0644)));
+}
+
+void test_core_ftruncate__cleanup(void)
+{
+	if (fd < 0)
+		return;
+
+	p_close(fd);
+	fd = 0;
+
+	p_unlink(filename);
+}
+
+static void _extend(git_off_t i64len)
+{
+	struct stat st;
+	int error;
+
+	cl_assert((error = p_ftruncate(fd, i64len)) == 0);
+	cl_assert((error = p_fstat(fd, &st)) == 0);
+	cl_assert(st.st_size == i64len);
+}
+
+void test_core_ftruncate__2gb(void)
+{
+	_extend(0x80000001);
+}
+
+void test_core_ftruncate__4gb(void)
+{
+	_extend(0x100000001);
+}