mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
190 lines
6.4 KiB
Go
190 lines
6.4 KiB
Go
// 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
|
||
|
|
}
|