github.com/demonoid81/moby@v0.0.0-20200517203328-62dd8e17c460/pkg/archive/archive_linux.go (about) 1 package archive // import "github.com/demonoid81/moby/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/demonoid81/moby/pkg/system" 14 "github.com/moby/sys/mount" 15 "github.com/pkg/errors" 16 "golang.org/x/sys/unix" 17 ) 18 19 func getWhiteoutConverter(format WhiteoutFormat, inUserNS bool) tarWhiteoutConverter { 20 if format == OverlayWhiteoutFormat { 21 return overlayWhiteoutConverter{inUserNS: inUserNS} 22 } 23 return nil 24 } 25 26 type overlayWhiteoutConverter struct { 27 inUserNS bool 28 } 29 30 func (overlayWhiteoutConverter) ConvertWrite(hdr *tar.Header, path string, fi os.FileInfo) (wo *tar.Header, err error) { 31 // convert whiteouts to AUFS format 32 if fi.Mode()&os.ModeCharDevice != 0 && hdr.Devmajor == 0 && hdr.Devminor == 0 { 33 // we just rename the file and make it normal 34 dir, filename := filepath.Split(hdr.Name) 35 hdr.Name = filepath.Join(dir, WhiteoutPrefix+filename) 36 hdr.Mode = 0600 37 hdr.Typeflag = tar.TypeReg 38 hdr.Size = 0 39 } 40 41 if fi.Mode()&os.ModeDir != 0 { 42 // convert opaque dirs to AUFS format by writing an empty file with the prefix 43 opaque, err := system.Lgetxattr(path, "trusted.overlay.opaque") 44 if err != nil { 45 return nil, err 46 } 47 if len(opaque) == 1 && opaque[0] == 'y' { 48 if hdr.Xattrs != nil { 49 delete(hdr.Xattrs, "trusted.overlay.opaque") 50 } 51 52 // create a header for the whiteout file 53 // it should inherit some properties from the parent, but be a regular file 54 wo = &tar.Header{ 55 Typeflag: tar.TypeReg, 56 Mode: hdr.Mode & int64(os.ModePerm), 57 Name: filepath.Join(hdr.Name, WhiteoutOpaqueDir), 58 Size: 0, 59 Uid: hdr.Uid, 60 Uname: hdr.Uname, 61 Gid: hdr.Gid, 62 Gname: hdr.Gname, 63 AccessTime: hdr.AccessTime, 64 ChangeTime: hdr.ChangeTime, 65 } 66 } 67 } 68 69 return 70 } 71 72 func (c overlayWhiteoutConverter) ConvertRead(hdr *tar.Header, path string) (bool, error) { 73 base := filepath.Base(path) 74 dir := filepath.Dir(path) 75 76 // if a directory is marked as opaque by the AUFS special file, we need to translate that to overlay 77 if base == WhiteoutOpaqueDir { 78 err := unix.Setxattr(dir, "trusted.overlay.opaque", []byte{'y'}, 0) 79 if err != nil { 80 if c.inUserNS { 81 if err = replaceDirWithOverlayOpaque(dir); err != nil { 82 return false, errors.Wrapf(err, "replaceDirWithOverlayOpaque(%q) failed", dir) 83 } 84 } else { 85 return false, errors.Wrapf(err, "setxattr(%q, trusted.overlay.opaque=y)", dir) 86 } 87 } 88 // don't write the file itself 89 return false, err 90 } 91 92 // if a file was deleted and we are using overlay, we need to create a character device 93 if strings.HasPrefix(base, WhiteoutPrefix) { 94 originalBase := base[len(WhiteoutPrefix):] 95 originalPath := filepath.Join(dir, originalBase) 96 97 if err := unix.Mknod(originalPath, unix.S_IFCHR, 0); err != nil { 98 if c.inUserNS { 99 // Ubuntu and a few distros support overlayfs in userns. 100 // 101 // Although we can't call mknod directly in userns (at least on bionic kernel 4.15), 102 // we can still create 0,0 char device using mknodChar0Overlay(). 103 // 104 // NOTE: we don't need this hack for the containerd snapshotter+unpack model. 105 if err := mknodChar0Overlay(originalPath); err != nil { 106 return false, errors.Wrapf(err, "failed to mknodChar0UserNS(%q)", originalPath) 107 } 108 } else { 109 return false, errors.Wrapf(err, "failed to mknod(%q, S_IFCHR, 0)", originalPath) 110 } 111 } 112 if err := os.Chown(originalPath, hdr.Uid, hdr.Gid); err != nil { 113 return false, err 114 } 115 116 // don't write the file itself 117 return false, nil 118 } 119 120 return true, nil 121 } 122 123 // mknodChar0Overlay creates 0,0 char device by mounting overlayfs and unlinking. 124 // This function can be used for creating 0,0 char device in userns on Ubuntu. 125 // 126 // Steps: 127 // * Mkdir lower,upper,merged,work 128 // * Create lower/dummy 129 // * Mount overlayfs 130 // * Unlink merged/dummy 131 // * Unmount overlayfs 132 // * Make sure a 0,0 char device is created as upper/dummy 133 // * Rename upper/dummy to cleansedOriginalPath 134 func mknodChar0Overlay(cleansedOriginalPath string) error { 135 dir := filepath.Dir(cleansedOriginalPath) 136 tmp, err := ioutil.TempDir(dir, "mc0o") 137 if err != nil { 138 return errors.Wrapf(err, "failed to create a tmp directory under %s", dir) 139 } 140 defer os.RemoveAll(tmp) 141 lower := filepath.Join(tmp, "l") 142 upper := filepath.Join(tmp, "u") 143 work := filepath.Join(tmp, "w") 144 merged := filepath.Join(tmp, "m") 145 for _, s := range []string{lower, upper, work, merged} { 146 if err := os.MkdirAll(s, 0700); err != nil { 147 return errors.Wrapf(err, "failed to mkdir %s", s) 148 } 149 } 150 dummyBase := "d" 151 lowerDummy := filepath.Join(lower, dummyBase) 152 if err := ioutil.WriteFile(lowerDummy, []byte{}, 0600); err != nil { 153 return errors.Wrapf(err, "failed to create a dummy lower file %s", lowerDummy) 154 } 155 // lowerdir needs ":" to be escaped: https://github.com/moby/moby/issues/40939#issuecomment-627098286 156 lowerEscaped := strings.ReplaceAll(lower, ":", "\\:") 157 mOpts := fmt.Sprintf("lowerdir=%s,upperdir=%s,workdir=%s", lowerEscaped, upper, work) 158 if err := mount.Mount("overlay", merged, "overlay", mOpts); err != nil { 159 return err 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 if err := mount.Mount("overlay", merged, "overlay", mOpts); err != nil { 245 return "", err 246 } 247 mergedDummy := filepath.Join(merged, dummyBase) 248 if err := os.Remove(mergedDummy); err != nil { 249 syscall.Unmount(merged, 0) 250 return "", errors.Wrapf(err, "failed to rmdir %s", mergedDummy) 251 } 252 // upperDummy becomes a 0,0-char device file here 253 if err := os.Mkdir(mergedDummy, 0700); err != nil { 254 syscall.Unmount(merged, 0) 255 return "", errors.Wrapf(err, "failed to mkdir %s", mergedDummy) 256 } 257 // upperDummy becomes a directory with trusted.overlay.opaque xattr 258 // (but can't be verified in userns) 259 if err := syscall.Unmount(merged, 0); err != nil { 260 return "", errors.Wrapf(err, "failed to unmount %s", merged) 261 } 262 upperDummy := filepath.Join(upper, dummyBase) 263 return upperDummy, nil 264 }