github.com/devdivbcp/moby@v17.12.0-ce-rc1.0.20200726071732-2d4bfdc789ad+incompatible/pkg/archive/archive_linux.go (about) 1 package archive // import "github.com/docker/docker/pkg/archive" 2 3 import ( 4 "archive/tar" 5 "fmt" 6 "io/ioutil" 7 "os" 8 "path/filepath" 9 "strings" 10 "syscall" 11 12 "github.com/containerd/continuity/fs" 13 "github.com/docker/docker/pkg/system" 14 "github.com/pkg/errors" 15 "golang.org/x/sys/unix" 16 ) 17 18 func getWhiteoutConverter(format WhiteoutFormat, inUserNS bool) tarWhiteoutConverter { 19 if format == OverlayWhiteoutFormat { 20 return overlayWhiteoutConverter{inUserNS: inUserNS} 21 } 22 return nil 23 } 24 25 type overlayWhiteoutConverter struct { 26 inUserNS bool 27 } 28 29 func (overlayWhiteoutConverter) ConvertWrite(hdr *tar.Header, path string, fi os.FileInfo) (wo *tar.Header, err error) { 30 // convert whiteouts to AUFS format 31 if fi.Mode()&os.ModeCharDevice != 0 && hdr.Devmajor == 0 && hdr.Devminor == 0 { 32 // we just rename the file and make it normal 33 dir, filename := filepath.Split(hdr.Name) 34 hdr.Name = filepath.Join(dir, WhiteoutPrefix+filename) 35 hdr.Mode = 0600 36 hdr.Typeflag = tar.TypeReg 37 hdr.Size = 0 38 } 39 40 if fi.Mode()&os.ModeDir != 0 { 41 // convert opaque dirs to AUFS format by writing an empty file with the prefix 42 opaque, err := system.Lgetxattr(path, "trusted.overlay.opaque") 43 if err != nil { 44 return nil, err 45 } 46 if len(opaque) == 1 && opaque[0] == 'y' { 47 if hdr.Xattrs != nil { 48 delete(hdr.Xattrs, "trusted.overlay.opaque") 49 } 50 51 // create a header for the whiteout file 52 // it should inherit some properties from the parent, but be a regular file 53 wo = &tar.Header{ 54 Typeflag: tar.TypeReg, 55 Mode: hdr.Mode & int64(os.ModePerm), 56 Name: filepath.Join(hdr.Name, WhiteoutOpaqueDir), 57 Size: 0, 58 Uid: hdr.Uid, 59 Uname: hdr.Uname, 60 Gid: hdr.Gid, 61 Gname: hdr.Gname, 62 AccessTime: hdr.AccessTime, 63 ChangeTime: hdr.ChangeTime, 64 } 65 } 66 } 67 68 return 69 } 70 71 func (c overlayWhiteoutConverter) ConvertRead(hdr *tar.Header, path string) (bool, error) { 72 base := filepath.Base(path) 73 dir := filepath.Dir(path) 74 75 // if a directory is marked as opaque by the AUFS special file, we need to translate that to overlay 76 if base == WhiteoutOpaqueDir { 77 err := unix.Setxattr(dir, "trusted.overlay.opaque", []byte{'y'}, 0) 78 if err != nil { 79 if c.inUserNS { 80 if err = replaceDirWithOverlayOpaque(dir); err != nil { 81 return false, errors.Wrapf(err, "replaceDirWithOverlayOpaque(%q) failed", dir) 82 } 83 } else { 84 return false, errors.Wrapf(err, "setxattr(%q, trusted.overlay.opaque=y)", dir) 85 } 86 } 87 // don't write the file itself 88 return false, err 89 } 90 91 // if a file was deleted and we are using overlay, we need to create a character device 92 if strings.HasPrefix(base, WhiteoutPrefix) { 93 originalBase := base[len(WhiteoutPrefix):] 94 originalPath := filepath.Join(dir, originalBase) 95 96 if err := unix.Mknod(originalPath, unix.S_IFCHR, 0); err != nil { 97 if c.inUserNS { 98 // Ubuntu and a few distros support overlayfs in userns. 99 // 100 // Although we can't call mknod directly in userns (at least on bionic kernel 4.15), 101 // we can still create 0,0 char device using mknodChar0Overlay(). 102 // 103 // NOTE: we don't need this hack for the containerd snapshotter+unpack model. 104 if err := mknodChar0Overlay(originalPath); err != nil { 105 return false, errors.Wrapf(err, "failed to mknodChar0UserNS(%q)", originalPath) 106 } 107 } else { 108 return false, errors.Wrapf(err, "failed to mknod(%q, S_IFCHR, 0)", originalPath) 109 } 110 } 111 if err := os.Chown(originalPath, hdr.Uid, hdr.Gid); err != nil { 112 return false, err 113 } 114 115 // don't write the file itself 116 return false, nil 117 } 118 119 return true, nil 120 } 121 122 // mknodChar0Overlay creates 0,0 char device by mounting overlayfs and unlinking. 123 // This function can be used for creating 0,0 char device in userns on Ubuntu. 124 // 125 // Steps: 126 // * Mkdir lower,upper,merged,work 127 // * Create lower/dummy 128 // * Mount overlayfs 129 // * Unlink merged/dummy 130 // * Unmount overlayfs 131 // * Make sure a 0,0 char device is created as upper/dummy 132 // * Rename upper/dummy to cleansedOriginalPath 133 func mknodChar0Overlay(cleansedOriginalPath string) error { 134 dir := filepath.Dir(cleansedOriginalPath) 135 tmp, err := ioutil.TempDir(dir, "mc0o") 136 if err != nil { 137 return errors.Wrapf(err, "failed to create a tmp directory under %s", dir) 138 } 139 defer os.RemoveAll(tmp) 140 lower := filepath.Join(tmp, "l") 141 upper := filepath.Join(tmp, "u") 142 work := filepath.Join(tmp, "w") 143 merged := filepath.Join(tmp, "m") 144 for _, s := range []string{lower, upper, work, merged} { 145 if err := os.MkdirAll(s, 0700); err != nil { 146 return errors.Wrapf(err, "failed to mkdir %s", s) 147 } 148 } 149 dummyBase := "d" 150 lowerDummy := filepath.Join(lower, dummyBase) 151 if err := ioutil.WriteFile(lowerDummy, []byte{}, 0600); err != nil { 152 return errors.Wrapf(err, "failed to create a dummy lower file %s", lowerDummy) 153 } 154 // lowerdir needs ":" to be escaped: https://github.com/moby/moby/issues/40939#issuecomment-627098286 155 lowerEscaped := strings.ReplaceAll(lower, ":", "\\:") 156 mOpts := fmt.Sprintf("lowerdir=%s,upperdir=%s,workdir=%s", lowerEscaped, upper, work) 157 // docker/pkg/mount.Mount() requires procfs to be mounted. So we use syscall.Mount() directly instead. 158 if err := syscall.Mount("overlay", merged, "overlay", uintptr(0), mOpts); err != nil { 159 return errors.Wrapf(err, "failed to mount overlay (%s) on %s", mOpts, merged) 160 } 161 mergedDummy := filepath.Join(merged, dummyBase) 162 if err := os.Remove(mergedDummy); err != nil { 163 syscall.Unmount(merged, 0) 164 return errors.Wrapf(err, "failed to unlink %s", mergedDummy) 165 } 166 if err := syscall.Unmount(merged, 0); err != nil { 167 return errors.Wrapf(err, "failed to unmount %s", merged) 168 } 169 upperDummy := filepath.Join(upper, dummyBase) 170 if err := isChar0(upperDummy); err != nil { 171 return err 172 } 173 if err := os.Rename(upperDummy, cleansedOriginalPath); err != nil { 174 return errors.Wrapf(err, "failed to rename %s to %s", upperDummy, cleansedOriginalPath) 175 } 176 return nil 177 } 178 179 func isChar0(path string) error { 180 osStat, err := os.Stat(path) 181 if err != nil { 182 return errors.Wrapf(err, "failed to stat %s", path) 183 } 184 st, ok := osStat.Sys().(*syscall.Stat_t) 185 if !ok { 186 return errors.Errorf("got unsupported stat for %s", path) 187 } 188 if os.FileMode(st.Mode)&syscall.S_IFMT != syscall.S_IFCHR { 189 return errors.Errorf("%s is not a character device, got mode=%d", path, st.Mode) 190 } 191 if st.Rdev != 0 { 192 return errors.Errorf("%s is not a 0,0 character device, got Rdev=%d", path, st.Rdev) 193 } 194 return nil 195 } 196 197 // replaceDirWithOverlayOpaque replaces path with a new directory with trusted.overlay.opaque 198 // xattr. The contents of the directory are preserved. 199 func replaceDirWithOverlayOpaque(path string) error { 200 if path == "/" { 201 return errors.New("replaceDirWithOverlayOpaque: path must not be \"/\"") 202 } 203 dir := filepath.Dir(path) 204 tmp, err := ioutil.TempDir(dir, "rdwoo") 205 if err != nil { 206 return errors.Wrapf(err, "failed to create a tmp directory under %s", dir) 207 } 208 defer os.RemoveAll(tmp) 209 // newPath is a new empty directory crafted with trusted.overlay.opaque xattr. 210 // we copy the content of path into newPath, remove path, and rename newPath to path. 211 newPath, err := createDirWithOverlayOpaque(tmp) 212 if err != nil { 213 return errors.Wrapf(err, "createDirWithOverlayOpaque(%q) failed", tmp) 214 } 215 if err := fs.CopyDir(newPath, path); err != nil { 216 return errors.Wrapf(err, "CopyDir(%q, %q) failed", newPath, path) 217 } 218 if err := os.RemoveAll(path); err != nil { 219 return err 220 } 221 return os.Rename(newPath, path) 222 } 223 224 // createDirWithOverlayOpaque creates a directory with trusted.overlay.opaque xattr, 225 // without calling setxattr, so as to allow creating opaque dir in userns on Ubuntu. 226 func createDirWithOverlayOpaque(tmp string) (string, error) { 227 lower := filepath.Join(tmp, "l") 228 upper := filepath.Join(tmp, "u") 229 work := filepath.Join(tmp, "w") 230 merged := filepath.Join(tmp, "m") 231 for _, s := range []string{lower, upper, work, merged} { 232 if err := os.MkdirAll(s, 0700); err != nil { 233 return "", errors.Wrapf(err, "failed to mkdir %s", s) 234 } 235 } 236 dummyBase := "d" 237 lowerDummy := filepath.Join(lower, dummyBase) 238 if err := os.MkdirAll(lowerDummy, 0700); err != nil { 239 return "", errors.Wrapf(err, "failed to create a dummy lower directory %s", lowerDummy) 240 } 241 // lowerdir needs ":" to be escaped: https://github.com/moby/moby/issues/40939#issuecomment-627098286 242 lowerEscaped := strings.ReplaceAll(lower, ":", "\\:") 243 mOpts := fmt.Sprintf("lowerdir=%s,upperdir=%s,workdir=%s", lowerEscaped, upper, work) 244 // docker/pkg/mount.Mount() requires procfs to be mounted. So we use syscall.Mount() directly instead. 245 if err := syscall.Mount("overlay", merged, "overlay", uintptr(0), mOpts); err != nil { 246 return "", errors.Wrapf(err, "failed to mount overlay (%s) on %s", mOpts, merged) 247 } 248 mergedDummy := filepath.Join(merged, dummyBase) 249 if err := os.Remove(mergedDummy); err != nil { 250 syscall.Unmount(merged, 0) 251 return "", errors.Wrapf(err, "failed to rmdir %s", mergedDummy) 252 } 253 // upperDummy becomes a 0,0-char device file here 254 if err := os.Mkdir(mergedDummy, 0700); err != nil { 255 syscall.Unmount(merged, 0) 256 return "", errors.Wrapf(err, "failed to mkdir %s", mergedDummy) 257 } 258 // upperDummy becomes a directory with trusted.overlay.opaque xattr 259 // (but can't be verified in userns) 260 if err := syscall.Unmount(merged, 0); err != nil { 261 return "", errors.Wrapf(err, "failed to unmount %s", merged) 262 } 263 upperDummy := filepath.Join(upper, dummyBase) 264 return upperDummy, nil 265 }