Repository: kubernetes-sigs/gcp-filestore-csi-driver (go)

Issue:
Handle NodePublish in compliance with CSI spec
Reference: https://github.com/kubernetes/kubernetes/issues/75535

During NodePublish, kubernetes today creates the target path (for e.g. /var/lib/kubelet/pods/<POD ID>/volumes/kubernetes.io~csi/<VolumeID>/mount).
This behavior will go away in future kubernetes version, where k8s would only create the parent dir  /var/lib/kubelet/pods/<POD ID>/volumes/kubernetes.iocsi/<VolumeID>

It is the driver's responsibility to ensure the target path is created (and delete during node unpublish).
Update the current node publish code, to check for existence of the target path and create one if needed (for linux), before proceeding to mount call.


Pull request:
Support NodeStageVolume for the driver
<!--  Thanks for sending a pull request!  Here are some tips for you:

1. If this is your first time, please read our contributor guidelines: https://git.k8s.io/community/contributors/guide#your-first-contribution and developer guide https://git.k8s.io/community/contributors/devel/development.md#development-guide
2. Follow the instructions for writing a release note: https://git.k8s.io/community/contributors/guide/release-notes.md
3. If the PR is unfinished, see how to mark it: https://git.k8s.io/community/contributors/guide/pull-requests.md#marking-unfinished-pull-requests
-->

**What type of PR is this?**
> Uncomment only one ` /kind <>` line, hit enter to put that in a new line, and remove leading whitespaces from that line:
>
> /kind api-change
> /kind bug
> /kind cleanup
> /kind design
> /kind documentation
> /kind failing-test
/kind feature
> /kind flake

**What this PR does / why we need it**:

**Which issue(s) this PR fixes**:
<!--
*Automatically closes linked issue when PR is merged.
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
_If PR is about `failing-tests or flakes`, please post the related issues/tests in a comment and do not use `Fixes`_*
-->
Fixes #47

**Special notes for your reviewer**:

**Does this PR introduce a user-facing change?**:
<!--
If no, just write "NONE" in the release-note block below.
If yes, a release note is required:
Enter your extended release note in the block below. If the PR requires additional action from users switching to the new release, include the string "action required".
-->
```release-note
Support NodeStageVolume for Filestore CSI driver, and NodePublish should create the target path if missing.
```


Patch:
diff --git a/pkg/cloud_provider/file/fake.go b/pkg/cloud_provider/file/fake.go
index b29f1e77..59084ac6 100644
--- a/pkg/cloud_provider/file/fake.go
+++ b/pkg/cloud_provider/file/fake.go
@@ -47,7 +47,7 @@ func (manager *fakeServiceManager) CreateInstance(ctx context.Context, obj *Serv
 		},
 		Network: Network{
 			Name:            obj.Network.Name,
-			Ip:              "test-ip",
+			Ip:              "1.1.1.1",
 			ReservedIpRange: obj.Network.ReservedIpRange,
 		},
 		Labels: obj.Labels,
diff --git a/pkg/csi_driver/gcfs_driver.go b/pkg/csi_driver/gcfs_driver.go
index e34253cf..bda51e1a 100644
--- a/pkg/csi_driver/gcfs_driver.go
+++ b/pkg/csi_driver/gcfs_driver.go
@@ -82,7 +82,11 @@ func NewGCFSDriver(config *GCFSDriverConfig) (*GCFSDriver, error) {
 	// Setup RPC servers
 	driver.ids = newIdentityServer(driver)
 	if config.RunNode {
+		nscap := []csi.NodeServiceCapability_RPC_Type{
+			csi.NodeServiceCapability_RPC_STAGE_UNSTAGE_VOLUME,
+		}
 		driver.ns = newNodeServer(driver, config.Mounter, config.Cloud.Meta)
+		driver.addNodeServiceCapabilities(nscap)
 	}
 	if config.RunController {
 		csc := []csi.ControllerServiceCapability_RPC_Type{
diff --git a/pkg/csi_driver/node.go b/pkg/csi_driver/node.go
index a2f7970d..e9419a00 100644
--- a/pkg/csi_driver/node.go
+++ b/pkg/csi_driver/node.go
@@ -18,6 +18,7 @@ package driver
 
 import (
 	"fmt"
+	"net"
 	"os"
 	"runtime"
 
@@ -55,57 +56,33 @@ func newNodeServer(driver *GCFSDriver, mounter mount.Interface, metaService meta
 	}
 }
 
-// NodePublishVolume mounts the GCFS volume
+// NodePublishVolume bind mounts from the source staging path, where the GCFS volume is mounted.
 func (s *nodeServer) NodePublishVolume(ctx context.Context, req *csi.NodePublishVolumeRequest) (*csi.NodePublishVolumeResponse, error) {
-	// TODO: make this idempotent. Multiple requests for the same volume can come in parallel, this needs to be seralized
-	// We need something like the nestedpendingoperations
-
 	// Validate arguments
 	readOnly := req.GetReadonly()
 	targetPath := req.GetTargetPath()
+	stagingTargetPath := req.GetStagingTargetPath()
 	if len(targetPath) == 0 {
 		return nil, status.Error(codes.InvalidArgument, "NodePublishVolume target path must be provided")
 	}
-
-	if err := s.driver.validateVolumeCapabilities([]*csi.VolumeCapability{req.GetVolumeCapability()}); err != nil {
-		return nil, status.Error(codes.InvalidArgument, err.Error())
+	if len(stagingTargetPath) == 0 {
+		return nil, status.Error(codes.InvalidArgument, "NodePublishVolume stagingTargetPath path must be provided")
 	}
 
-	// Validate volume attributes
-	attr := req.GetVolumeContext()
-	if err := validateVolumeAttributes(attr); err != nil {
+	if err := s.driver.validateVolumeCapabilities([]*csi.VolumeCapability{req.GetVolumeCapability()}); err != nil {
 		return nil, status.Error(codes.InvalidArgument, err.Error())
 	}
 
-	// TODO: If target path does not exist create it and then proceed to mount.
-	// Check kubernetes/kubernetes#75535. CO may create only the parent directory.
-	mounted, err := s.isDirMounted(targetPath)
-	if err != nil && !os.IsNotExist(err) {
-		return nil, err
-	}
-
-	if mounted {
-		// Already mounted
-		// TODO: validate it's the correct mount
-		return &csi.NodePublishVolumeResponse{}, nil
-	}
-
-	// Mount source
-	source := fmt.Sprintf("%s:/%s", attr[attrIP], attr[attrVolume])
-
+	var err error
 	// FileSystem type
 	fstype := "nfs"
-
 	// Mount options
-	options := []string{}
-
-	// Windows specific values
+	options := []string{"bind"}
+	// Windows specific values (TODO: Revisit windows specific logic for bind mount)
 	if goOs == "windows" {
-		source = fmt.Sprintf("\\\\%s\\%s", attr[attrIP], attr[attrVolume])
 		fstype = "cifs"
 
 		// Login credentials
-
 		secrets := req.GetSecrets()
 		if err := validateSmbNodePublishSecrets(secrets); err != nil {
 			return nil, status.Error(codes.InvalidArgument, err.Error())
@@ -118,6 +95,22 @@ func (s *nodeServer) NodePublishVolume(ctx context.Context, req *csi.NodePublish
 		if err := os.Remove(targetPath); err != nil && !os.IsNotExist(err) {
 			return nil, err
 		}
+	} else {
+		// TODO: If target path does not exist create it and then proceed to mount.
+		// (https://github.com/kubernetes-sigs/gcp-filestore-csi-driver/issues/47)
+		// Check kubernetes/kubernetes#75535. CO may create only the parent directory.
+		mounted, err := s.isDirMounted(targetPath)
+		if err != nil && !os.IsNotExist(err) {
+			return nil, err
+		}
+		if mounted {
+			return &csi.NodePublishVolumeResponse{}, nil
+		}
+		if os.IsNotExist(err) {
+			if mkdirErr := os.MkdirAll(targetPath, 0750); mkdirErr != nil {
+				return nil, status.Error(codes.Internal, fmt.Sprintf("mkdir failed on path %s (%v)", targetPath, mkdirErr))
+			}
+		}
 	}
 
 	if readOnly {
@@ -127,12 +120,13 @@ func (s *nodeServer) NodePublishVolume(ctx context.Context, req *csi.NodePublish
 		options = append(options, capMount.GetMountFlags()...)
 	}
 
-	err = s.mounter.Mount(source, targetPath, fstype, options)
+	err = s.mounter.Mount(stagingTargetPath, targetPath, fstype, options)
 	if err != nil {
 		glog.Errorf("Mount %q failed, cleaning up", targetPath)
-		if unmntErr := s.unmountPath(targetPath); unmntErr != nil {
+		if unmntErr := mount.CleanupMountPoint(stagingTargetPath, s.mounter, false /* extensiveMountPointCheck */); unmntErr != nil {
 			glog.Errorf("Unmount %q failed: %v", targetPath, unmntErr)
 		}
+
 		return nil, status.Error(codes.Internal, fmt.Sprintf("mount %q failed: %v", targetPath, err))
 	}
 
@@ -142,15 +136,13 @@ func (s *nodeServer) NodePublishVolume(ctx context.Context, req *csi.NodePublish
 
 // NodeUnpublishVolume unmounts the GCFS volume
 func (s *nodeServer) NodeUnpublishVolume(ctx context.Context, req *csi.NodeUnpublishVolumeRequest) (*csi.NodeUnpublishVolumeResponse, error) {
-	// TODO: make this idempotent
-
 	// Validate arguments
 	targetPath := req.GetTargetPath()
 	if len(targetPath) == 0 {
 		return nil, status.Error(codes.InvalidArgument, "NodeUnpublishVolume target path must be provided")
 	}
 
-	if err := s.unmountPath(targetPath); err != nil {
+	if err := mount.CleanupMountPoint(targetPath, s.mounter, false /* extensiveMountPointCheck */); err != nil {
 		return nil, status.Error(codes.Internal, err.Error())
 	}
 
@@ -172,11 +164,147 @@ func (s *nodeServer) NodeGetCapabilities(ctx context.Context, req *csi.NodeGetCa
 	}, nil
 }
 
+func (s *nodeServer) NodeStageVolume(ctx context.Context, req *csi.NodeStageVolumeRequest) (*csi.NodeStageVolumeResponse, error) {
+	// Validate Arguments
+	volumeID := req.GetVolumeId()
+	stagingTargetPath := req.GetStagingTargetPath()
+	volumeCapability := req.GetVolumeCapability()
+	if len(volumeID) == 0 {
+		return nil, status.Error(codes.InvalidArgument, "NodeStageVolume Volume ID must be provided")
+	}
+	if len(stagingTargetPath) == 0 {
+		return nil, status.Error(codes.InvalidArgument, "NodeStageVolume Staging Target Path must be provided")
+	}
+	if volumeCapability == nil {
+		return nil, status.Error(codes.InvalidArgument, "NodeStageVolume Volume Capability must be provided")
+	}
+
+	if err := validateVolumeCapability(volumeCapability); err != nil {
+		return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("VolumeCapability is invalid: %v", err))
+	}
+
+	// Validate volume attributes
+	attr := req.GetVolumeContext()
+	if err := validateVolumeAttributes(attr); err != nil {
+		return nil, status.Error(codes.InvalidArgument, err.Error())
+	}
+	// Mount source
+	source := fmt.Sprintf("%s:/%s", attr[attrIP], attr[attrVolume])
+	mounted, err := s.isDirMounted(stagingTargetPath)
+	needsCreateDir := false
+	if err != nil {
+		if os.IsNotExist(err) {
+			needsCreateDir = true
+		} else {
+			return nil, err
+		}
+	}
+
+	if mounted {
+		// Already mounted
+		glog.V(4).Infof("NodeStageVolume succeeded on volume %v to staging target path %s, mount already exists.", volumeID, stagingTargetPath)
+		return &csi.NodeStageVolumeResponse{}, nil
+	}
+
+	if needsCreateDir {
+		glog.V(4).Infof("NodeStageVolume attempting mkdir for path %s", stagingTargetPath)
+		if err := os.MkdirAll(stagingTargetPath, 0750); err != nil {
+			return nil, fmt.Errorf("mkdir failed for path %s (%v)", stagingTargetPath, err)
+		}
+	}
+
+	fstype := "nfs"
+	options := []string{}
+	if mnt := volumeCapability.GetMount(); mnt != nil {
+		for _, flag := range mnt.MountFlags {
+			options = append(options, flag)
+		}
+	}
+
+	err = s.mounter.Mount(source, stagingTargetPath, fstype, options)
+	if err != nil {
+		glog.Errorf("Mount %q failed, cleaning up", stagingTargetPath)
+		if unmntErr := mount.CleanupMountPoint(stagingTargetPath, s.mounter, false /* extensiveMountPointCheck */); unmntErr != nil {
+			glog.Errorf("Unmount %q failed: %v", stagingTargetPath, unmntErr)
+		}
+		return nil, status.Error(codes.Internal, fmt.Sprintf("mount %q failed: %v", stagingTargetPath, err))
+	}
+
+	glog.V(4).Infof("NodeStageVolume succeeded on volume %v to path %s", volumeID, stagingTargetPath)
+	return &csi.NodeStageVolumeResponse{}, nil
+}
+
+func (s *nodeServer) NodeUnstageVolume(ctx context.Context, req *csi.NodeUnstageVolumeRequest) (*csi.NodeUnstageVolumeResponse, error) {
+	// Validate arguments
+	volumeID := req.GetVolumeId()
+	stagingTargetPath := req.GetStagingTargetPath()
+	if len(volumeID) == 0 {
+		return nil, status.Error(codes.InvalidArgument, "NodeUnstageVolume Volume ID must be provided")
+	}
+	if len(stagingTargetPath) == 0 {
+		return nil, status.Error(codes.InvalidArgument, "NodeUnstageVolume Staging Target Path must be provided")
+	}
+
+	if err := mount.CleanupMountPoint(stagingTargetPath, s.mounter, false /* extensiveMountPointCheck */); err != nil {
+		return nil, status.Error(codes.Internal, err.Error())
+	}
+
+	glog.V(4).Infof("NodeUnstageVolume succeeded on volume %v from staging target path %s", volumeID, stagingTargetPath)
+	return &csi.NodeUnstageVolumeResponse{}, nil
+}
+
+func validateVolumeCapability(vc *csi.VolumeCapability) error {
+	if err := validateAccessMode(vc.GetAccessMode()); err != nil {
+		return err
+	}
+
+	blk := vc.GetBlock()
+	mnt := vc.GetMount()
+	if mnt == nil && blk == nil {
+		return fmt.Errorf("must specify an access type")
+	}
+
+	if mnt != nil && blk != nil {
+		return fmt.Errorf("specified both mount and block access types")
+	}
+
+	if blk != nil {
+		return fmt.Errorf("Block access type not supported")
+	}
+	return nil
+}
+
+func validateAccessMode(am *csi.VolumeCapability_AccessMode) error {
+	if am == nil {
+		return fmt.Errorf("access mode is nil")
+	}
+
+	switch am.GetMode() {
+	case csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER:
+	case csi.VolumeCapability_AccessMode_SINGLE_NODE_READER_ONLY:
+	case csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY:
+	case csi.VolumeCapability_AccessMode_MULTI_NODE_SINGLE_WRITER:
+	case csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER:
+	default:
+		return fmt.Errorf("Unkown access mode %v", am.GetMode())
+	}
+	return nil
+}
+
 // validateVolumeAttributes checks for all the necessary fields for mounting the volume
 func validateVolumeAttributes(attr map[string]string) error {
-	// TODO: validate ip syntax
-	if attr[attrIP] == "" {
-		return fmt.Errorf("volume attribute %v not set", attrIP)
+	instanceip, ok := attr[attrIP]
+	if !ok {
+		return fmt.Errorf("volume attribute key %v not set", attrIP)
+	}
+	// Check for valid IPV4 address.
+	if net.ParseIP(instanceip) == nil {
+		return fmt.Errorf("invalid IP address %v in volume attributes", instanceip)
+	}
+
+	_, ok = attr[attrVolume]
+	if !ok {
+		return fmt.Errorf("volume attribute key %v not set", attrVolume)
 	}
 	// TODO: validate allowed characters
 	if attr[attrVolume] == "" {
@@ -211,24 +339,3 @@ func (s *nodeServer) isDirMounted(targetPath string) (bool, error) {
 	}
 	return false, nil
 }
-
-// unmountPath unmounts the given path if it a mount point
-func (s *nodeServer) unmountPath(targetPath string) error {
-	mounted, err := s.isDirMounted(targetPath)
-	if os.IsNotExist(err) {
-		// Volume already unmounted
-		glog.V(4).Infof("Mount point %q already unmounted", targetPath)
-		return nil
-	} else if err != nil {
-		return fmt.Errorf("failed to check mount point %q: %v", targetPath, err)
-	}
-
-	if mounted {
-		glog.V(4).Infof("Unmounting %q", targetPath)
-		err := s.mounter.Unmount(targetPath)
-		if err != nil {
-			return fmt.Errorf("unmount %q failed: %v", targetPath, err)
-		}
-	}
-	return nil
-}
diff --git a/pkg/csi_driver/node_unimpl.go b/pkg/csi_driver/node_unimpl.go
index f7e22f60..4fa56c7e 100644
--- a/pkg/csi_driver/node_unimpl.go
+++ b/pkg/csi_driver/node_unimpl.go
@@ -23,14 +23,6 @@ import (
 	"google.golang.org/grpc/status"
 )
 
-func (s *nodeServer) NodeStageVolume(ctx context.Context, req *csi.NodeStageVolumeRequest) (*csi.NodeStageVolumeResponse, error) {
-	return nil, status.Error(codes.Unimplemented, "NodeStageVolume unsupported")
-}
-
-func (s *nodeServer) NodeUnstageVolume(ctx context.Context, req *csi.NodeUnstageVolumeRequest) (*csi.NodeUnstageVolumeResponse, error) {
-	return nil, status.Error(codes.Unimplemented, "NodeUnStageVolume unsupported")
-}
-
 func (s *nodeServer) NodeExpandVolume(ctx context.Context, req *csi.NodeExpandVolumeRequest) (*csi.NodeExpandVolumeResponse, error) {
 	return nil, status.Error(codes.Unimplemented, "NodeUnStageVolume unsupported")
 }


Test patch:
diff --git a/pkg/csi_driver/controller_test.go b/pkg/csi_driver/controller_test.go
index 3a9b16b8..f2184a5b 100644
--- a/pkg/csi_driver/controller_test.go
+++ b/pkg/csi_driver/controller_test.go
@@ -31,7 +31,7 @@ import (
 const (
 	testProject          = "test-project"
 	testLocation         = "test-location"
-	testIP               = "test-ip"
+	testIP               = "1.1.1.1"
 	testCSIVolume        = "test-csi"
 	testVolumeID         = "modeInstance/test-location/test-csi/vol1"
 	testReservedIPV4CIDR = "192.168.92.0/26"
diff --git a/pkg/csi_driver/node_test.go b/pkg/csi_driver/node_test.go
index 1a953c36..c350de48 100644
--- a/pkg/csi_driver/node_test.go
+++ b/pkg/csi_driver/node_test.go
@@ -81,8 +81,8 @@ func TestNodePublishVolume(t *testing.T) {
 	if err = os.MkdirAll(testTargetPath, defaultPerm); err != nil {
 		t.Fatalf("failed to setup target path: %v", err)
 	}
+	stagingTargetPath := filepath.Join(base, "staging")
 	defer os.RemoveAll(base)
-
 	cases := []struct {
 		name          string
 		mounts        []mount.MountPoint // already existing mounts
@@ -99,30 +99,33 @@ func TestNodePublishVolume(t *testing.T) {
 		{
 			name: "valid request not already mounted",
 			req: &csi.NodePublishVolumeRequest{
-				VolumeId:         testVolumeID,
-				TargetPath:       testTargetPath,
-				VolumeCapability: testVolumeCapability,
-				VolumeContext:    testVolumeAttributes,
+				VolumeId:          testVolumeID,
+				StagingTargetPath: stagingTargetPath,
+				TargetPath:        testTargetPath,
+				VolumeCapability:  testVolumeCapability,
+				VolumeContext:     testVolumeAttributes,
 			},
 			actions:       []mount.FakeAction{{Action: mount.FakeActionMount}},
-			expectedMount: &mount.MountPoint{Device: testDevice, Path: testTargetPath, Type: "nfs"},
+			expectedMount: &mount.MountPoint{Device: stagingTargetPath, Path: testTargetPath, Type: "nfs", Opts: []string{"bind"}},
 		},
 		{
 			name:   "valid request already mounted",
 			mounts: []mount.MountPoint{{Device: "/test-device", Path: testTargetPath}},
 			req: &csi.NodePublishVolumeRequest{
-				VolumeId:         testVolumeID,
-				TargetPath:       testTargetPath,
-				VolumeCapability: testVolumeCapability,
-				VolumeContext:    testVolumeAttributes,
+				VolumeId:          testVolumeID,
+				StagingTargetPath: stagingTargetPath,
+				TargetPath:        testTargetPath,
+				VolumeCapability:  testVolumeCapability,
+				VolumeContext:     testVolumeAttributes,
 			},
 			expectedMount: &mount.MountPoint{Device: "/test-device", Path: testTargetPath},
 		},
 		{
 			name: "valid request with user mount options",
 			req: &csi.NodePublishVolumeRequest{
-				VolumeId:   testVolumeID,
-				TargetPath: testTargetPath,
+				VolumeId:          testVolumeID,
+				StagingTargetPath: stagingTargetPath,
+				TargetPath:        testTargetPath,
 				VolumeCapability: &csi.VolumeCapability{
 					AccessType: &csi.VolumeCapability_Mount{
 						Mount: &csi.VolumeCapability_MountVolume{
@@ -137,24 +140,36 @@ func TestNodePublishVolume(t *testing.T) {
 			},
 			actions: []mount.FakeAction{{Action: mount.FakeActionMount}},
 
-			expectedMount: &mount.MountPoint{Device: testDevice, Path: testTargetPath, Type: "nfs", Opts: []string{"foo", "bar"}},
+			expectedMount: &mount.MountPoint{Device: stagingTargetPath, Path: testTargetPath, Type: "nfs", Opts: []string{"bind", "foo", "bar"}},
 		},
 		{
 			name: "valid request read only",
 			req: &csi.NodePublishVolumeRequest{
-				VolumeId:         testVolumeID,
-				TargetPath:       testTargetPath,
-				VolumeCapability: testVolumeCapability,
-				VolumeContext:    testVolumeAttributes,
-				Readonly:         true,
+				VolumeId:          testVolumeID,
+				StagingTargetPath: stagingTargetPath,
+				TargetPath:        testTargetPath,
+				VolumeCapability:  testVolumeCapability,
+				VolumeContext:     testVolumeAttributes,
+				Readonly:          true,
 			},
 			actions:       []mount.FakeAction{{Action: mount.FakeActionMount}},
-			expectedMount: &mount.MountPoint{Device: testDevice, Path: testTargetPath, Type: "nfs", Opts: []string{"ro"}},
+			expectedMount: &mount.MountPoint{Device: stagingTargetPath, Path: testTargetPath, Type: "nfs", Opts: []string{"bind", "ro"}},
 		},
 		{
 			name: "empty target path",
+			req: &csi.NodePublishVolumeRequest{
+				VolumeId:          testVolumeID,
+				StagingTargetPath: stagingTargetPath,
+				VolumeCapability:  testVolumeCapability,
+				VolumeContext:     testVolumeAttributes,
+			},
+			expectErr: true,
+		},
+		{
+			name: "empty staging target path",
 			req: &csi.NodePublishVolumeRequest{
 				VolumeId:         testVolumeID,
+				TargetPath:       testTargetPath,
 				VolumeCapability: testVolumeCapability,
 				VolumeContext:    testVolumeAttributes,
 			},
@@ -178,7 +193,7 @@ func TestNodePublishVolume(t *testing.T) {
 			},
 			expectErr: true,
 		},
-		// TODO: Revisit this.
+		// TODO: Revisit this (https://github.com/kubernetes-sigs/gcp-filestore-csi-driver/issues/47).
 		// {
 		// 	name: "target path doesn't exist",
 		// 	req: &csi.NodePublishVolumeRequest{
@@ -212,7 +227,8 @@ func TestNodePublishVolume(t *testing.T) {
 	}
 }
 
-func TestWindowsNodePublishVolume(t *testing.T) {
+// TODO: Revisit windows tests
+func testWindowsNodePublishVolume(t *testing.T) {
 	defaultPerm := os.FileMode(0750) + os.ModeDir
 	defaultOsString := goOs