| /* |
| 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 erofs |
| |
| import ( |
| "bytes" |
| "context" |
| "fmt" |
| "os" |
| "path/filepath" |
| "syscall" |
| |
| "github.com/containerd/continuity/fs" |
| "github.com/containerd/log" |
| "github.com/containerd/plugin" |
| "golang.org/x/sys/unix" |
| |
| "github.com/containerd/containerd/v2/core/mount" |
| "github.com/containerd/containerd/v2/internal/erofsutils" |
| ) |
| |
| // check if EROFS kernel filesystem is registered or not |
| func findErofs() bool { |
| fs, err := os.ReadFile("/proc/filesystems") |
| if err != nil { |
| return false |
| } |
| return bytes.Contains(fs, []byte("\terofs\n")) |
| } |
| |
| func checkCompatibility(root string) error { |
| supportsDType, err := fs.SupportsDType(root) |
| if err != nil { |
| return err |
| } |
| if !supportsDType { |
| return fmt.Errorf("%s does not support d_type. If the backing filesystem is xfs, please reformat with ftype=1 to enable d_type support", root) |
| } |
| |
| if !findErofs() { |
| return fmt.Errorf("EROFS unsupported, please `modprobe erofs`: %w", plugin.ErrSkipPlugin) |
| } |
| |
| return nil |
| } |
| |
| func setImmutable(path string, enable bool) error { |
| //nolint:revive,staticcheck // silence "don't use ALL_CAPS in Go names; use CamelCase" |
| const ( |
| FS_IMMUTABLE_FL = 0x10 |
| ) |
| f, err := os.Open(path) |
| if err != nil { |
| return fmt.Errorf("failed to open: %w", err) |
| } |
| defer f.Close() |
| |
| oldattr, err := unix.IoctlGetInt(int(f.Fd()), unix.FS_IOC_GETFLAGS) |
| if err != nil { |
| return fmt.Errorf("error getting inode flags: %w", err) |
| } |
| newattr := oldattr | FS_IMMUTABLE_FL |
| if !enable { |
| newattr ^= FS_IMMUTABLE_FL |
| } |
| if newattr == oldattr { |
| return nil |
| } |
| return unix.IoctlSetPointerInt(int(f.Fd()), unix.FS_IOC_SETFLAGS, newattr) |
| } |
| |
| func cleanupUpper(upper string) error { |
| if err := mount.UnmountAll(upper, 0); err != nil { |
| return fmt.Errorf("failed to unmount EROFS mount on %v: %w", upper, err) |
| } |
| return nil |
| } |
| |
| func convertDirToErofs(ctx context.Context, layerBlob, upperDir string) error { |
| err := erofsutils.ConvertErofs(ctx, layerBlob, upperDir, nil) |
| if err != nil { |
| return err |
| } |
| |
| // Remove all sub-directories in the overlayfs upperdir. Leave the |
| // overlayfs upperdir itself since it's used for Lchown. |
| fd, err := os.Open(upperDir) |
| if err != nil { |
| return err |
| } |
| defer fd.Close() |
| |
| dirs, err := fd.Readdirnames(0) |
| if err != nil { |
| return err |
| } |
| |
| for _, d := range dirs { |
| dir := filepath.Join(upperDir, d) |
| if err := os.RemoveAll(dir); err != nil { |
| log.G(ctx).WithError(err).WithField("path", dir).Warn("failed to remove directory") |
| } |
| } |
| return nil |
| } |
| |
| func upperDirectoryPermission(p, parent string) error { |
| st, err := os.Stat(parent) |
| if err != nil { |
| return fmt.Errorf("failed to stat parent: %w", err) |
| } |
| |
| stat := st.Sys().(*syscall.Stat_t) |
| if err := os.Lchown(p, int(stat.Uid), int(stat.Gid)); err != nil { |
| return fmt.Errorf("failed to chown: %w", err) |
| } |
| |
| return nil |
| } |