| /* | |
| Copyright The containerd Authors. | |
| Licensed under the Apache License, Version 2.0 (the "License"); | |
| you may not use this file except in compliance with the License. | |
| You may obtain a copy of the License at | |
| http://www.apache.org/licenses/LICENSE-2.0 | |
| Unless required by applicable law or agreed to in writing, software | |
| distributed under the License is distributed on an "AS IS" BASIS, | |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| See the License for the specific language governing permissions and | |
| limitations under the License. | |
| */ | |
| package fs | |
| import ( | |
| "errors" | |
| "fmt" | |
| "os" | |
| "path/filepath" | |
| "syscall" | |
| "github.com/containerd/continuity/devices" | |
| "github.com/containerd/continuity/sysx" | |
| "golang.org/x/sys/unix" | |
| ) | |
| const ( | |
| // whiteoutPrefix prefix means file is a whiteout. If this is followed | |
| // by a filename this means that file has been removed from the base | |
| // layer. | |
| // | |
| // See https://github.com/opencontainers/image-spec/blob/master/layer.md#whiteouts | |
| whiteoutPrefix = ".wh." | |
| ) | |
| // overlayFSWhiteoutConvert detects whiteouts and opaque directories. | |
| // | |
| // It returns deleted indicator if the file is a character device with 0/0 | |
| // device number. And call changeFn with ChangeKindDelete for opaque | |
| // directories. | |
| // | |
| // Check: https://www.kernel.org/doc/Documentation/filesystems/overlayfs.txt | |
| func overlayFSWhiteoutConvert(diffDir, path string, f os.FileInfo, changeFn ChangeFunc) (deleted bool, _ error) { | |
| if f.Mode()&os.ModeCharDevice != 0 { | |
| if _, ok := f.Sys().(*syscall.Stat_t); !ok { | |
| return false, nil | |
| } | |
| major, minor, err := devices.DeviceInfo(f) | |
| if err != nil { | |
| return false, err | |
| } | |
| return (major == 0 && minor == 0), nil | |
| } | |
| if f.IsDir() { | |
| originalPath := filepath.Join(diffDir, path) | |
| opaque, err := getOpaqueValue(originalPath) | |
| if err != nil { | |
| if errors.Is(err, unix.ENODATA) { | |
| return false, nil | |
| } | |
| return false, err | |
| } | |
| if len(opaque) == 1 && opaque[0] == 'y' { | |
| opaqueDirPath := filepath.Join(path, whiteoutPrefix+".opq") | |
| return false, changeFn(ChangeKindDelete, opaqueDirPath, nil, nil) | |
| } | |
| } | |
| return false, nil | |
| } | |
| // getOpaqueValue returns opaque value for a given file. | |
| func getOpaqueValue(filePath string) ([]byte, error) { | |
| for _, xattr := range []string{ | |
| "trusted.overlay.opaque", | |
| // TODO(fuweid): | |
| // | |
| // user.overlay.* is available since 5.11. We should check | |
| // kernel version before read. | |
| // | |
| // REF: https://github.com/torvalds/linux/commit/2d2f2d7322ff43e0fe92bf8cccdc0b09449bf2e1 | |
| "user.overlay.opaque", | |
| } { | |
| opaque, err := sysx.LGetxattr(filePath, xattr) | |
| if err != nil { | |
| if errors.Is(err, unix.ENODATA) || errors.Is(err, unix.ENOTSUP) { | |
| continue | |
| } | |
| return nil, fmt.Errorf("failed to retrieve %s attr: %w", xattr, err) | |
| } | |
| return opaque, nil | |
| } | |
| return nil, unix.ENODATA | |
| } |