gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/pkg/devutil/devutil.go (about)

     1  // Copyright 2023 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package devutil provides device specific utilities.
    16  package devutil
    17  
    18  import (
    19  	"fmt"
    20  
    21  	"golang.org/x/sys/unix"
    22  	"gvisor.dev/gvisor/pkg/context"
    23  	"gvisor.dev/gvisor/pkg/fsutil"
    24  	"gvisor.dev/gvisor/pkg/lisafs"
    25  	"gvisor.dev/gvisor/pkg/log"
    26  	"gvisor.dev/gvisor/pkg/unet"
    27  )
    28  
    29  // GoferClient is the lisafs client for the /dev gofer connection.
    30  type GoferClient struct {
    31  	clientFD lisafs.ClientFD
    32  	hostFD   int
    33  	contName string
    34  }
    35  
    36  // NewGoferClient establishes the LISAFS connection to the dev gofer server.
    37  // It takes ownership of fd. contName is the owning container name.
    38  func NewGoferClient(ctx context.Context, contName string, fd int) (*GoferClient, error) {
    39  	ctx.UninterruptibleSleepStart(false)
    40  	defer ctx.UninterruptibleSleepFinish(false)
    41  
    42  	sock, err := unet.NewSocket(fd)
    43  	if err != nil {
    44  		ctx.Warningf("failed to create socket for dev gofer client: %v", err)
    45  		return nil, err
    46  	}
    47  	client, devInode, devHostFD, err := lisafs.NewClient(sock)
    48  	if err != nil {
    49  		ctx.Warningf("failed to create dev gofer client: %v", err)
    50  		return nil, err
    51  	}
    52  	return &GoferClient{
    53  		clientFD: client.NewFD(devInode.ControlFD),
    54  		hostFD:   devHostFD,
    55  		contName: contName,
    56  	}, nil
    57  }
    58  
    59  // Close closes the LISAFS connection.
    60  func (g *GoferClient) Close() {
    61  	// Close the connection to the server. This implicitly closes all FDs.
    62  	g.clientFD.Client().Close()
    63  	if g.hostFD >= 0 {
    64  		_ = unix.Close(g.hostFD)
    65  	}
    66  }
    67  
    68  // ContainerName returns the name of the container that owns this gofer.
    69  func (g *GoferClient) ContainerName() string {
    70  	return g.contName
    71  }
    72  
    73  // DirentNames returns names of all the dirents for /dev on the gofer.
    74  func (g *GoferClient) DirentNames(ctx context.Context) ([]string, error) {
    75  	if g.hostFD >= 0 {
    76  		return fsutil.DirentNames(g.hostFD)
    77  	}
    78  	client := g.clientFD.Client()
    79  	openFDID, _, err := g.clientFD.OpenAt(ctx, unix.O_RDONLY)
    80  	if err != nil {
    81  		return nil, fmt.Errorf("failed to open dev from gofer: %v", err)
    82  	}
    83  	defer client.CloseFD(ctx, openFDID, true /* flush */)
    84  	openFD := client.NewFD(openFDID)
    85  	const count = int32(64 * 1024)
    86  	var names []string
    87  	for {
    88  		dirents, err := openFD.Getdents64(ctx, count)
    89  		if err != nil {
    90  			return nil, fmt.Errorf("Getdents64 RPC failed: %v", err)
    91  		}
    92  		if len(dirents) == 0 {
    93  			break
    94  		}
    95  		for i := range dirents {
    96  			names = append(names, string(dirents[i].Name))
    97  		}
    98  	}
    99  	return names, nil
   100  }
   101  
   102  // OpenAt opens the device file at /dev/{name} on the gofer.
   103  func (g *GoferClient) OpenAt(ctx context.Context, name string, flags uint32) (int, error) {
   104  	flags &= unix.O_ACCMODE
   105  	if g.hostFD >= 0 {
   106  		return unix.Openat(g.hostFD, name, int(flags|unix.O_NOFOLLOW), 0)
   107  	}
   108  	childInode, err := g.clientFD.Walk(ctx, name)
   109  	if err != nil {
   110  		log.Infof("failed to walk %q from dev gofer FD", name)
   111  		return 0, err
   112  	}
   113  	client := g.clientFD.Client()
   114  	childFD := client.NewFD(childInode.ControlFD)
   115  
   116  	childOpenFD, childHostFD, err := childFD.OpenAt(ctx, flags)
   117  	if err != nil {
   118  		log.Infof("failed to open %q from child FD", name)
   119  		client.CloseFD(ctx, childFD.ID(), true /* flush */)
   120  		return 0, err
   121  	}
   122  	client.CloseFD(ctx, childFD.ID(), false /* flush */)
   123  	client.CloseFD(ctx, childOpenFD, true /* flush */)
   124  	return childHostFD, nil
   125  }
   126  
   127  // GoferClientProvider provides a GoferClient for a given container.
   128  type GoferClientProvider interface {
   129  	GetDevGoferClient(contName string) *GoferClient
   130  }