From 7b70cb1dbc448a35297bb6ae4595315b9947e0ce Mon Sep 17 00:00:00 2001
From: John Starich <johnstarich@johnstarich.com>
Date: Sat, 11 Jul 2020 18:13:38 -0500
Subject: [PATCH 1/3] Fix MemMapFs.Chmod changing non-perm bits

---
 memmap.go      | 22 ++++++++++++++++++----
 memmap_test.go | 31 +++++++++++++++++++++++++++++++
 2 files changed, 49 insertions(+), 4 deletions(-)

diff --git a/memmap.go b/memmap.go
index b7b1474b..39b1e75d 100644
--- a/memmap.go
+++ b/memmap.go
@@ -141,9 +141,7 @@ func (m *MemMapFs) Mkdir(name string, perm os.FileMode) error {
 	m.registerWithParent(item)
 	m.mu.Unlock()
 
-	m.Chmod(name, perm|os.ModeDir)
-
-	return nil
+	return m.unrestrictedChmod(name, perm|os.ModeDir)
 }
 
 func (m *MemMapFs) MkdirAll(path string, perm os.FileMode) error {
@@ -240,7 +238,7 @@ func (m *MemMapFs) OpenFile(name string, flag int, perm os.FileMode) (File, erro
 		}
 	}
 	if chmod {
-		m.Chmod(name, perm)
+		return file, m.unrestrictedChmod(name, perm)
 	}
 	return file, nil
 }
@@ -321,6 +319,22 @@ func (m *MemMapFs) Stat(name string) (os.FileInfo, error) {
 }
 
 func (m *MemMapFs) Chmod(name string, mode os.FileMode) error {
+	const chmodBits = os.ModePerm | os.ModeSetuid | os.ModeSetgid | os.ModeSticky // Only a subset of bits are allowed to be changed. Documented under os.Chmod()
+	mode &= chmodBits
+
+	m.mu.RLock()
+	f, ok := m.getData()[name]
+	m.mu.RUnlock()
+	if !ok {
+		return &os.PathError{Op: "chmod", Path: name, Err: ErrFileNotFound}
+	}
+	prevOtherBits := mem.GetFileInfo(f).Mode() & ^chmodBits
+
+	mode = prevOtherBits | mode
+	return m.unrestrictedChmod(name, mode)
+}
+
+func (m *MemMapFs) unrestrictedChmod(name string, mode os.FileMode) error {
 	name = normalizePath(name)
 
 	m.mu.RLock()
diff --git a/memmap_test.go b/memmap_test.go
index a88ea30b..76a712db 100644
--- a/memmap_test.go
+++ b/memmap_test.go
@@ -472,3 +472,34 @@ func TestMemFsUnexpectedEOF(t *testing.T) {
 		t.Fatal("Expected ErrUnexpectedEOF")
 	}
 }
+
+func TestMemFsChmod(t *testing.T) {
+	t.Parallel()
+
+	fs := NewMemMapFs()
+	const file = "/hello"
+	if err := fs.Mkdir(file, 0700); err != nil {
+		t.Fatal(err)
+	}
+
+	info, err := fs.Stat(file)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if info.Mode().String() != "drwx------" {
+		t.Fatal("mkdir failed to create a directory: mode =", info.Mode())
+	}
+
+	err = fs.Chmod(file, 0)
+	if err != nil {
+		t.Error("Failed to run chmod:", err)
+	}
+
+	info, err = fs.Stat(file)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if info.Mode().String() != "d---------" {
+		t.Error("chmod should not change file type. New mode =", info.Mode())
+	}
+}

From 57ab25aed5652c5c9691ac2544958c488b7f0961 Mon Sep 17 00:00:00 2001
From: John Starich <johnstarich@johnstarich.com>
Date: Sat, 11 Jul 2020 20:42:35 -0500
Subject: [PATCH 2/3] Use more appropriate func name for unrestricted mode
 changes

---
 memmap.go | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/memmap.go b/memmap.go
index 39b1e75d..6be0e9c2 100644
--- a/memmap.go
+++ b/memmap.go
@@ -141,7 +141,7 @@ func (m *MemMapFs) Mkdir(name string, perm os.FileMode) error {
 	m.registerWithParent(item)
 	m.mu.Unlock()
 
-	return m.unrestrictedChmod(name, perm|os.ModeDir)
+	return m.setFileMode(name, perm|os.ModeDir)
 }
 
 func (m *MemMapFs) MkdirAll(path string, perm os.FileMode) error {
@@ -238,7 +238,7 @@ func (m *MemMapFs) OpenFile(name string, flag int, perm os.FileMode) (File, erro
 		}
 	}
 	if chmod {
-		return file, m.unrestrictedChmod(name, perm)
+		return file, m.setFileMode(name, perm)
 	}
 	return file, nil
 }
@@ -331,10 +331,10 @@ func (m *MemMapFs) Chmod(name string, mode os.FileMode) error {
 	prevOtherBits := mem.GetFileInfo(f).Mode() & ^chmodBits
 
 	mode = prevOtherBits | mode
-	return m.unrestrictedChmod(name, mode)
+	return m.setFileMode(name, mode)
 }
 
-func (m *MemMapFs) unrestrictedChmod(name string, mode os.FileMode) error {
+func (m *MemMapFs) setFileMode(name string, mode os.FileMode) error {
 	name = normalizePath(name)
 
 	m.mu.RLock()

From d443df9ff3dfff432351d0938ee654c482291f6c Mon Sep 17 00:00:00 2001
From: John Starich <johnstarich@johnstarich.com>
Date: Sat, 11 Jul 2020 20:57:46 -0500
Subject: [PATCH 3/3] Fix test for Windows

---
 memmap_test.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/memmap_test.go b/memmap_test.go
index 76a712db..371096ff 100644
--- a/memmap_test.go
+++ b/memmap_test.go
@@ -477,7 +477,7 @@ func TestMemFsChmod(t *testing.T) {
 	t.Parallel()
 
 	fs := NewMemMapFs()
-	const file = "/hello"
+	const file = "hello"
 	if err := fs.Mkdir(file, 0700); err != nil {
 		t.Fatal(err)
 	}