github.com/jwhonce/docker@v0.6.7-0.20190327063223-da823cf3a5a3/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 mOpts := fmt.Sprintf("lowerdir=%s,upperdir=%s,workdir=%s", lower, upper, work) 155 // docker/pkg/mount.Mount() requires procfs to be mounted. So we use syscall.Mount() directly instead. 156 if err := syscall.Mount("overlay", merged, "overlay", uintptr(0), mOpts); err != nil { 157 return errors.Wrapf(err, "failed to mount overlay (%s) on %s", mOpts, merged) 158 } 159 mergedDummy := filepath.Join(merged, dummyBase) 160 if err := os.Remove(mergedDummy); err != nil { 161 syscall.Unmount(merged, 0) 162 return errors.Wrapf(err, "failed to unlink %s", mergedDummy) 163 } 164 if err := syscall.Unmount(merged, 0); err != nil { 165 return errors.Wrapf(err, "failed to unmount %s", merged) 166 } 167 upperDummy := filepath.Join(upper, dummyBase) 168 if err := isChar0(upperDummy); err != nil { 169 return err 170 } 171 if err := os.Rename(upperDummy, cleansedOriginalPath); err != nil { 172 return errors.Wrapf(err, "failed to rename %s to %s", upperDummy, cleansedOriginalPath) 173 } 174 return nil 175 } 176 177 func isChar0(path string) error { 178 osStat, err := os.Stat(path) 179 if err != nil { 180 return errors.Wrapf(err, "failed to stat %s", path) 181 } 182 st, ok := osStat.Sys().(*syscall.Stat_t) 183 if !ok { 184 return errors.Errorf("got unsupported stat for %s", path) 185 } 186 if os.FileMode(st.Mode)&syscall.S_IFMT != syscall.S_IFCHR { 187 return errors.Errorf("%s is not a character device, got mode=%d", path, st.Mode) 188 } 189 if st.Rdev != 0 { 190 return errors.Errorf("%s is not a 0,0 character device, got Rdev=%d", path, st.Rdev) 191 } 192 return nil 193 } 194 195 // replaceDirWithOverlayOpaque replaces path with a new directory with trusted.overlay.opaque 196 // xattr. The contents of the directory are preserved. 197 func replaceDirWithOverlayOpaque(path string) error { 198 if path == "/" { 199 return errors.New("replaceDirWithOverlayOpaque: path must not be \"/\"") 200 } 201 dir := filepath.Dir(path) 202 tmp, err := ioutil.TempDir(dir, "rdwoo") 203 if err != nil { 204 return errors.Wrapf(err, "failed to create a tmp directory under %s", dir) 205 } 206 defer os.RemoveAll(tmp) 207 // newPath is a new empty directory crafted with trusted.overlay.opaque xattr. 208 // we copy the content of path into newPath, remove path, and rename newPath to path. 209 newPath, err := createDirWithOverlayOpaque(tmp) 210 if err != nil { 211 return errors.Wrapf(err, "createDirWithOverlayOpaque(%q) failed", tmp) 212 } 213 if err := fs.CopyDir(newPath, path); err != nil { 214 return errors.Wrapf(err, "CopyDir(%q, %q) failed", newPath, path) 215 } 216 if err := os.RemoveAll(path); err != nil { 217 return err 218 } 219 return os.Rename(newPath, path) 220 } 221 222 // createDirWithOverlayOpaque creates a directory with trusted.overlay.opaque xattr, 223 // without calling setxattr, so as to allow creating opaque dir in userns on Ubuntu. 224 func createDirWithOverlayOpaque(tmp string) (string, error) { 225 lower := filepath.Join(tmp, "l") 226 upper := filepath.Join(tmp, "u") 227 work := filepath.Join(tmp, "w") 228 merged := filepath.Join(tmp, "m") 229 for _, s := range []string{lower, upper, work, merged} { 230 if err := os.MkdirAll(s, 0700); err != nil { 231 return "", errors.Wrapf(err, "failed to mkdir %s", s) 232 } 233 } 234 dummyBase := "d" 235 lowerDummy := filepath.Join(lower, dummyBase) 236 if err := os.MkdirAll(lowerDummy, 0700); err != nil { 237 return "", errors.Wrapf(err, "failed to create a dummy lower directory %s", lowerDummy) 238 } 239 mOpts := fmt.Sprintf("lowerdir=%s,upperdir=%s,workdir=%s", lower, upper, work) 240 // docker/pkg/mount.Mount() requires procfs to be mounted. So we use syscall.Mount() directly instead. 241 if err := syscall.Mount("overlay", merged, "overlay", uintptr(0), mOpts); err != nil { 242 return "", errors.Wrapf(err, "failed to mount overlay (%s) on %s", mOpts, merged) 243 } 244 mergedDummy := filepath.Join(merged, dummyBase) 245 if err := os.Remove(mergedDummy); err != nil { 246 syscall.Unmount(merged, 0) 247 return "", errors.Wrapf(err, "failed to rmdir %s", mergedDummy) 248 } 249 // upperDummy becomes a 0,0-char device file here 250 if err := os.Mkdir(mergedDummy, 0700); err != nil { 251 syscall.Unmount(merged, 0) 252 return "", errors.Wrapf(err, "failed to mkdir %s", mergedDummy) 253 } 254 // upperDummy becomes a directory with trusted.overlay.opaque xattr 255 // (but can't be verified in userns) 256 if err := syscall.Unmount(merged, 0); err != nil { 257 return "", errors.Wrapf(err, "failed to unmount %s", merged) 258 } 259 upperDummy := filepath.Join(upper, dummyBase) 260 return upperDummy, nil 261 }