Merge pull request #330 from pkg/issue-295-corrupt-resumes

Issue 295 corrupt resumes
diff --git a/request-interfaces.go b/request-interfaces.go
index 06106af..dd224cd 100644
--- a/request-interfaces.go
+++ b/request-interfaces.go
@@ -25,6 +25,8 @@
 // The request server code will call Close() on the returned io.WriterAt
 // ojbect if an io.Closer type assertion succeeds.
 // Note in cases of an error, the error text will be sent to the client.
+// Note when receiving an Append flag it is important to not open files using
+// O_APPEND if you plan to use WriteAt, as they conflict.
 // Called for Methods: Put, Open
 type FileWriter interface {
 	Filewrite(*Request) (io.WriterAt, error)
diff --git a/server.go b/server.go
index e7d6d81..905b75c 100644
--- a/server.go
+++ b/server.go
@@ -398,9 +398,9 @@
 		return statusFromError(p, syscall.EINVAL)
 	}
 
-	if p.hasPflags(sshFxfAppend) {
-		osFlags |= os.O_APPEND
-	}
+	// Don't use O_APPEND flag as it conflicts with WriteAt.
+	// The sshFxfAppend flag is a no-op here as the client sends the offsets.
+
 	if p.hasPflags(sshFxfCreat) {
 		osFlags |= os.O_CREATE
 	}
diff --git a/server_integration_test.go b/server_integration_test.go
index 384b6e4..1499dd8 100644
--- a/server_integration_test.go
+++ b/server_integration_test.go
@@ -649,6 +649,53 @@
 	}
 }
 
+func TestServerResume(t *testing.T) {
+	listenerGo, hostGo, portGo := testServer(t, GolangSFTP, READONLY)
+	defer listenerGo.Close()
+
+	tmpFileLocal := "/tmp/" + randName()
+	tmpFileRemote := "/tmp/" + randName()
+	defer os.RemoveAll(tmpFileLocal)
+	defer os.RemoveAll(tmpFileRemote)
+
+	t.Logf("put: local %v remote %v", tmpFileLocal, tmpFileRemote)
+
+	// create a local file with random contents to be pushed to the server
+	tmpFileLocalData := randData(2 * 1024 * 1024)
+	// only write half the data to simulate a split upload
+	half := 1024 * 1024
+	err := ioutil.WriteFile(tmpFileLocal, tmpFileLocalData[:half], 0644)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// sftp the first half of the file to the server
+	output, err := runSftpClient(t, "put "+tmpFileLocal+" "+tmpFileRemote,
+		"/", hostGo, portGo)
+	if err != nil {
+		t.Fatalf("runSftpClient failed: %v, output\n%v\n", err, output)
+	}
+
+	// write the full file out
+	err = ioutil.WriteFile(tmpFileLocal, tmpFileLocalData, 0644)
+	if err != nil {
+		t.Fatal(err)
+	}
+	// re-sftp the full file with the append flag set
+	output, err = runSftpClient(t, "put -a "+tmpFileLocal+" "+tmpFileRemote,
+		"/", hostGo, portGo)
+	if err != nil {
+		t.Fatalf("runSftpClient failed: %v, output\n%v\n", err, output)
+	}
+
+	// tmpFileRemote should now exist, with the same contents
+	if tmpFileRemoteData, err := ioutil.ReadFile(tmpFileRemote); err != nil {
+		t.Fatal(err)
+	} else if string(tmpFileLocalData) != string(tmpFileRemoteData) {
+		t.Fatal("contents of file incorrect after put")
+	}
+}
+
 func TestServerGet(t *testing.T) {
 	listenerGo, hostGo, portGo := testServer(t, GolangSFTP, READONLY)
 	defer listenerGo.Close()