mmap: allow mapping a zero-sized file

Fixes golang/go#56261

Change-Id: Iff450c457edf2b83b24b35199a2643ef9fed48ab
Reviewed-on: https://go-review.googlesource.com/c/exp/+/443576
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
Reviewed-by: Nigel Tao (INACTIVE; USE @golang.org INSTEAD) <nigeltao@google.com>
diff --git a/mmap/manual_test_program.go b/mmap/manual_test_program.go
index a1ab17b..5c212b6 100644
--- a/mmap/manual_test_program.go
+++ b/mmap/manual_test_program.go
@@ -24,6 +24,10 @@
 var garbage []byte
 
 func main() {
+	// If you replace "manual_test_program.go" with the name of an empty (zero
+	// sized) file (and set "const debug = true") then you will not necessarily
+	// see two "munmap log messages", since some operating systems will not
+	// allow a zero sized mapping so there is no need for a finalizer to unmap.
 	const filename = "manual_test_program.go"
 
 	for _, explicitClose := range []bool{false, true} {
@@ -31,6 +35,7 @@
 		if err != nil {
 			log.Fatalf("Open: %v", err)
 		}
+		println("Open succeeded; Len =", r.Len())
 		if explicitClose {
 			r.Close()
 		} else {
diff --git a/mmap/mmap_unix.go b/mmap/mmap_unix.go
index ae12499..5ceb69b 100644
--- a/mmap/mmap_unix.go
+++ b/mmap/mmap_unix.go
@@ -38,6 +38,9 @@
 func (r *ReaderAt) Close() error {
 	if r.data == nil {
 		return nil
+	} else if len(r.data) == 0 {
+		r.data = nil
+		return nil
 	}
 	data := r.data
 	r.data = nil
@@ -91,7 +94,14 @@
 
 	size := fi.Size()
 	if size == 0 {
-		return &ReaderAt{}, nil
+		// Treat (size == 0) as a special case, avoiding the syscall, since
+		// "man 2 mmap" says "the length... must be greater than 0".
+		//
+		// As we do not call syscall.Mmap, there is no need to call
+		// runtime.SetFinalizer to enforce a balancing syscall.Munmap.
+		return &ReaderAt{
+			data: make([]byte, 0),
+		}, nil
 	}
 	if size < 0 {
 		return nil, fmt.Errorf("mmap: file %q has negative size", filename)
diff --git a/mmap/mmap_windows.go b/mmap/mmap_windows.go
index d898828..ea1d1cb 100644
--- a/mmap/mmap_windows.go
+++ b/mmap/mmap_windows.go
@@ -36,6 +36,9 @@
 func (r *ReaderAt) Close() error {
 	if r.data == nil {
 		return nil
+	} else if len(r.data) == 0 {
+		r.data = nil
+		return nil
 	}
 	data := r.data
 	r.data = nil
@@ -89,7 +92,14 @@
 
 	size := fi.Size()
 	if size == 0 {
-		return &ReaderAt{}, nil
+		// Treat (size == 0) as a special case, avoiding the syscall, to be
+		// consistent with mmap_unix.go.
+		//
+		// As we do not call syscall.MapViewOfFile, there is no need to call
+		// runtime.SetFinalizer to enforce a balancing syscall.UnmapViewOfFile.
+		return &ReaderAt{
+			data: make([]byte, 0),
+		}, nil
 	}
 	if size < 0 {
 		return nil, fmt.Errorf("mmap: file %q has negative size", filename)