Browse code

idtools don't chown if not needed

In some cases (e.g. NFS), a chown may technically be a no-op but still
return `EPERM`, so only call `chown` when neccessary.

This is particularly problematic for docker users bind-mounting an NFS
share into a container.

Signed-off-by: Brian Goff <cpuguy83@gmail.com>

Brian Goff authored on 2017/10/02 22:47:09
Showing 1 changed files
... ...
@@ -26,14 +26,19 @@ func mkdirAs(path string, mode os.FileMode, ownerUID, ownerGID int, mkAll, chown
26 26
 	// so that we can chown all of them properly at the end.  If chownExisting is false, we won't
27 27
 	// chown the full directory path if it exists
28 28
 	var paths []string
29
-	if _, err := os.Stat(path); err != nil && os.IsNotExist(err) {
30
-		paths = []string{path}
31
-	} else if err == nil && chownExisting {
29
+
30
+	stat, err := system.Stat(path)
31
+	if err == nil {
32
+		if !chownExisting {
33
+			return nil
34
+		}
35
+
32 36
 		// short-circuit--we were called with an existing directory and chown was requested
33
-		return os.Chown(path, ownerUID, ownerGID)
34
-	} else if err == nil {
35
-		// nothing to do; directory path fully exists already and chown was NOT requested
36
-		return nil
37
+		return lazyChown(path, ownerUID, ownerGID, stat)
38
+	}
39
+
40
+	if os.IsNotExist(err) {
41
+		paths = []string{path}
37 42
 	}
38 43
 
39 44
 	if mkAll {
... ...
@@ -60,7 +65,7 @@ func mkdirAs(path string, mode os.FileMode, ownerUID, ownerGID int, mkAll, chown
60 60
 	// even if it existed, we will chown the requested path + any subpaths that
61 61
 	// didn't exist when we called MkdirAll
62 62
 	for _, pathComponent := range paths {
63
-		if err := os.Chown(pathComponent, ownerUID, ownerGID); err != nil {
63
+		if err := lazyChown(pathComponent, ownerUID, ownerGID, nil); err != nil {
64 64
 			return err
65 65
 		}
66 66
 	}
... ...
@@ -202,3 +207,20 @@ func callGetent(args string) (io.Reader, error) {
202 202
 	}
203 203
 	return bytes.NewReader(out), nil
204 204
 }
205
+
206
+// lazyChown performs a chown only if the uid/gid don't match what's requested
207
+// Normally a Chown is a no-op if uid/gid match, but in some cases this can still cause an error, e.g. if the
208
+// dir is on an NFS share, so don't call chown unless we absolutely must.
209
+func lazyChown(p string, uid, gid int, stat *system.StatT) error {
210
+	if stat == nil {
211
+		var err error
212
+		stat, err = system.Stat(p)
213
+		if err != nil {
214
+			return err
215
+		}
216
+	}
217
+	if stat.UID() == uint32(uid) && stat.GID() == uint32(gid) {
218
+		return nil
219
+	}
220
+	return os.Chown(p, uid, gid)
221
+}