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>
| ... | ... |
@@ -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 |
+} |