// Copyright 2023 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 devutil provides device specific utilities. package devutil import ( "fmt" "golang.org/x/sys/unix" "gvisor.dev/gvisor/pkg/context" "gvisor.dev/gvisor/pkg/errors/linuxerr" "gvisor.dev/gvisor/pkg/fspath" "gvisor.dev/gvisor/pkg/fsutil" "gvisor.dev/gvisor/pkg/lisafs" "gvisor.dev/gvisor/pkg/log" "gvisor.dev/gvisor/pkg/unet" ) // GoferClient is the lisafs client for the /dev gofer connection. type GoferClient struct { clientFD lisafs.ClientFD hostFD int contName string } // NewGoferClient establishes the LISAFS connection to the dev gofer server. // It takes ownership of fd. contName is the owning container name. func NewGoferClient(ctx context.Context, contName string, fd int) (*GoferClient, error) { ctx.UninterruptibleSleepStart() defer ctx.UninterruptibleSleepFinish() sock, err := unet.NewSocket(fd) if err != nil { ctx.Warningf("failed to create socket for dev gofer client: %v", err) return nil, err } client, devInode, devHostFD, err := lisafs.NewClient(sock) if err != nil { ctx.Warningf("failed to create dev gofer client: %v", err) return nil, err } return &GoferClient{ clientFD: client.NewFD(devInode.ControlFD), hostFD: devHostFD, contName: contName, }, nil } // Close closes the LISAFS connection. func (g *GoferClient) Close() { // Close the connection to the server. This implicitly closes all FDs. g.clientFD.Client().Close() if g.hostFD >= 0 { _ = unix.Close(g.hostFD) } } // ContainerName returns the name of the container that owns this gofer. func (g *GoferClient) ContainerName() string { return g.contName } // DirentNames returns names of all the dirents for /dev on the gofer. func (g *GoferClient) DirentNames(ctx context.Context) ([]string, error) { if g.hostFD >= 0 { return fsutil.DirentNames(g.hostFD) } client := g.clientFD.Client() openFDID, _, err := g.clientFD.OpenAt(ctx, unix.O_RDONLY) if err != nil { return nil, fmt.Errorf("failed to open dev from gofer: %v", err) } defer client.CloseFD(ctx, openFDID, true /* flush */) openFD := client.NewFD(openFDID) const count = int32(64 * 1024) var names []string for { dirents, err := openFD.Getdents64(ctx, count) if err != nil { return nil, fmt.Errorf("Getdents64 RPC failed: %v", err) } if len(dirents) == 0 { break } for i := range dirents { names = append(names, string(dirents[i].Name)) } } return names, nil } // OpenAt opens the device file at /dev/{relpath} on the gofer. func (g *GoferClient) OpenAt(ctx context.Context, relpath string, flags uint32) (int, error) { flags &= unix.O_ACCMODE if g.hostFD >= 0 { return unix.Openat(g.hostFD, relpath, int(flags|unix.O_NOFOLLOW), 0) } pathComponents := make([]string, 0, 1) for pit := fspath.Parse(relpath).Begin; pit.Ok(); pit = pit.Next() { pathComponents = append(pathComponents, pit.String()) } status, inodes, err := g.clientFD.WalkMultiple(ctx, pathComponents) if err != nil { log.Infof("failed to walk %q from dev gofer FD", relpath) return 0, err } client := g.clientFD.Client() numInodes := len(inodes) // Close intermediate FDs. for i := range numInodes - 1 { tmpFD := client.NewFD(inodes[i].ControlFD) tmpFD.Close(ctx, false /* flush */) } if status != lisafs.WalkSuccess { if numInodes != 0 { lastFD := client.NewFD(inodes[numInodes-1].ControlFD) lastFD.Close(ctx, true /* flush */) } log.Infof("failed to walk %q from dev gofer FD: after %d path components: %v", relpath, numInodes, status) return 0, linuxerr.ENOENT } childFD := client.NewFD(inodes[numInodes-1].ControlFD) childOpenFD, childHostFD, err := childFD.OpenAt(ctx, flags) if err != nil { log.Infof("failed to open %q from child FD", relpath) client.CloseFD(ctx, childFD.ID(), true /* flush */) return 0, err } client.CloseFD(ctx, childFD.ID(), false /* flush */) client.CloseFD(ctx, childOpenFD, true /* flush */) return childHostFD, nil } // GoferClientProvider provides a GoferClient for a given container. type GoferClientProvider interface { GetDevGoferClient(contName string) *GoferClient }