// Copyright 2020 The gVisor 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 dev provides a filesystem implementation for /dev. package dev import ( "fmt" "path" "gvisor.dev/gvisor/pkg/abi/linux" "gvisor.dev/gvisor/pkg/context" "gvisor.dev/gvisor/pkg/errors/linuxerr" "gvisor.dev/gvisor/pkg/fspath" "gvisor.dev/gvisor/pkg/sentry/fsimpl/tmpfs" "gvisor.dev/gvisor/pkg/sentry/kernel/auth" "gvisor.dev/gvisor/pkg/sentry/vfs" ) // Name is the dev filesystem name. const Name = "dev" // FilesystemType implements vfs.FilesystemType. // // +stateify savable type FilesystemType struct{} // Name implements vfs.FilesystemType.Name. func (FilesystemType) Name() string { return Name } // GetFilesystem implements vfs.FilesystemType.GetFilesystem. func (fst FilesystemType) GetFilesystem(ctx context.Context, vfsObj *vfs.VirtualFilesystem, creds *auth.Credentials, source string, opts vfs.GetFilesystemOptions) (*vfs.Filesystem, *vfs.Dentry, error) { mntns, err := vfsObj.NewMountNamespace(ctx, creds, source /* source */, tmpfs.Name, &vfs.MountOptions{GetFilesystemOptions: vfs.GetFilesystemOptions{ Data: "mode=0755", // opts from drivers/base/devtmpfs.c:devtmpfs_init() }}, nil) if err != nil { return nil, nil, err } defer mntns.DecRef(ctx) root := mntns.Root(ctx) defer root.DecRef(ctx) iopts, _ := opts.InternalData.(InternalData) // If not provided, zero value is OK. // Initialize contents. if err := userspaceInit(ctx, vfsObj, creds, root, iopts.ShmMode); err != nil { return nil, nil, err } if err := vfsObj.ForEachDevice(func(pathname string, kind vfs.DeviceKind, major, minor uint32, perms uint16) error { if pathname == "" { return nil } mode := linux.FileMode(perms) switch kind { case vfs.CharDevice: mode |= linux.S_IFCHR case vfs.BlockDevice: mode |= linux.S_IFBLK default: panic(fmt.Sprintf("invalid DeviceKind: %v", kind)) } return CreateDeviceFile(ctx, vfsObj, creds, root, pathname, major, minor, mode, nil /* uid */, nil /* gid */) }); err != nil { return nil, nil, err } root.Mount().Filesystem().IncRef() root.Dentry().IncRef() return root.Mount().Filesystem(), root.Dentry(), nil } // Release implements vfs.FilesystemType.Release. func (fst *FilesystemType) Release(ctx context.Context) {} // InternalData contains internal data passed in via vfs.GetFilesystemOptions. type InternalData struct { // ShmMode indicates the mode to create the /dev/shm dir with. ShmMode *uint16 } func pathOperationAt(root vfs.VirtualDentry, pathname string) *vfs.PathOperation { return &vfs.PathOperation{ Root: root, Start: root, Path: fspath.Parse(pathname), } } // CreateDeviceFile creates a device special file at the given pathname from root. func CreateDeviceFile(ctx context.Context, vfsObj *vfs.VirtualFilesystem, creds *auth.Credentials, root vfs.VirtualDentry, pathname string, major, minor uint32, mode linux.FileMode, uid, gid *uint32) error { // Create any parent directories. See // devtmpfs.c:handle_create()=>create_path(). parent := path.Dir(pathname) if err := vfsObj.MkdirAllAt(ctx, parent, root, creds, &vfs.MkdirOptions{ Mode: 0755, }, true /* mustBeDir */); err != nil { return fmt.Errorf("failed to create device parent directory %q: %v", parent, err) } created := true pop := pathOperationAt(root, pathname) if err := vfsObj.MknodAt(ctx, creds, pop, &vfs.MknodOptions{Mode: mode, DevMajor: major, DevMinor: minor}); err != nil { if linuxerr.Equals(linuxerr.EEXIST, err) { // EEXIST is silently ignored; compare // opencontainers/runc:libcontainer/rootfs_linux.go:createDeviceNode(). created = false } else { return fmt.Errorf("failed to create device file at %q: %w", pathname, err) } } if created && (uid != nil || gid != nil) { var opts vfs.SetStatOptions if uid != nil { opts.Stat.Mask |= linux.STATX_UID opts.Stat.UID = *uid } if gid != nil { opts.Stat.Mask |= linux.STATX_GID opts.Stat.GID = *gid } if err := vfsObj.SetStatAt(ctx, creds, pop, &opts); err != nil { return fmt.Errorf("failed to set UID/GID for device file %q: %w", pathname, err) } } return nil } // userspaceInit creates symbolic links and mount points in the devtmpfs // instance that are created by userspace in Linux. It does not create mounts. func userspaceInit(ctx context.Context, vfsObj *vfs.VirtualFilesystem, creds *auth.Credentials, root vfs.VirtualDentry, shmMode *uint16) error { // Initialize symlinks. for _, symlink := range []struct { source string target string }{ // systemd: src/shared/dev-setup.c:dev_setup() {source: "fd", target: "/proc/self/fd"}, {source: "stdin", target: "/proc/self/fd/0"}, {source: "stdout", target: "/proc/self/fd/1"}, {source: "stderr", target: "/proc/self/fd/2"}, // /proc/kcore is not implemented. // Linux implements /dev/ptmx as a device node, but advises // container implementations to create /dev/ptmx as a symlink // to pts/ptmx (Documentation/filesystems/devpts.txt). Systemd // follows this advice (src/nspawn/nspawn.c:setup_pts()), while // LXC tries to create a bind mount and falls back to a symlink // (src/lxc/conf.c:lxc_setup_devpts()). {source: "ptmx", target: "pts/ptmx"}, } { if err := vfsObj.SymlinkAt(ctx, creds, pathOperationAt(root, symlink.source), symlink.target); err != nil { return fmt.Errorf("failed to create symlink %q => %q: %v", symlink.source, symlink.target, err) } } // systemd: src/core/mount-setup.c:mount_table for _, dir := range []string{ "shm", "pts", } { // "The access mode here doesn't really matter too much, since the // mounted file system will take precedence anyway" // - systemd: src/core/mount-setup.c:mount_one() accessMode := linux.FileMode(0755) if shmMode != nil && dir == "shm" { accessMode = linux.FileMode(*shmMode) } if err := vfsObj.MkdirAt(ctx, creds, pathOperationAt(root, dir), &vfs.MkdirOptions{ Mode: accessMode, }); err != nil { return fmt.Errorf("failed to create directory %q: %v", dir, err) } } return nil }