github.com/sagernet/gvisor@v0.0.0-20240428053021-e691de28565f/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 "github.com/sagernet/gvisor/pkg/context" 23 "github.com/sagernet/gvisor/pkg/fsutil" 24 "github.com/sagernet/gvisor/pkg/lisafs" 25 "github.com/sagernet/gvisor/pkg/log" 26 "github.com/sagernet/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 } 34 35 // NewGoferClient establishes the LISAFS connection to the dev gofer server. 36 // It takes ownership of fd. 37 func NewGoferClient(ctx context.Context, fd int) (*GoferClient, error) { 38 ctx.UninterruptibleSleepStart(false) 39 defer ctx.UninterruptibleSleepFinish(false) 40 41 sock, err := unet.NewSocket(fd) 42 if err != nil { 43 ctx.Warningf("failed to create socket for dev gofer client: %v", err) 44 return nil, err 45 } 46 client, devInode, devHostFD, err := lisafs.NewClient(sock) 47 if err != nil { 48 ctx.Warningf("failed to create dev gofer client: %v", err) 49 return nil, err 50 } 51 return &GoferClient{ 52 clientFD: client.NewFD(devInode.ControlFD), 53 hostFD: devHostFD, 54 }, nil 55 } 56 57 // Close closes the LISAFS connection. 58 func (g *GoferClient) Close() { 59 // Close the connection to the server. This implicitly closes all FDs. 60 g.clientFD.Client().Close() 61 if g.hostFD >= 0 { 62 _ = unix.Close(g.hostFD) 63 } 64 } 65 66 // DirentNames returns names of all the dirents for /dev on the gofer. 67 func (g *GoferClient) DirentNames(ctx context.Context) ([]string, error) { 68 if g.hostFD >= 0 { 69 return fsutil.DirentNames(g.hostFD) 70 } 71 client := g.clientFD.Client() 72 openFDID, _, err := g.clientFD.OpenAt(ctx, unix.O_RDONLY) 73 if err != nil { 74 return nil, fmt.Errorf("failed to open dev from gofer: %v", err) 75 } 76 defer client.CloseFD(ctx, openFDID, true /* flush */) 77 openFD := client.NewFD(openFDID) 78 const count = int32(64 * 1024) 79 var names []string 80 for { 81 dirents, err := openFD.Getdents64(ctx, count) 82 if err != nil { 83 return nil, fmt.Errorf("Getdents64 RPC failed: %v", err) 84 } 85 if len(dirents) == 0 { 86 break 87 } 88 for i := range dirents { 89 names = append(names, string(dirents[i].Name)) 90 } 91 } 92 return names, nil 93 } 94 95 // OpenAt opens the device file at /dev/{name} on the gofer. 96 func (g *GoferClient) OpenAt(ctx context.Context, name string, flags uint32) (int, error) { 97 flags &= unix.O_ACCMODE 98 if g.hostFD >= 0 { 99 return unix.Openat(g.hostFD, name, int(flags|unix.O_NOFOLLOW), 0) 100 } 101 childInode, err := g.clientFD.Walk(ctx, name) 102 if err != nil { 103 log.Infof("failed to walk %q from dev gofer FD", name) 104 return 0, err 105 } 106 client := g.clientFD.Client() 107 childFD := client.NewFD(childInode.ControlFD) 108 109 childOpenFD, childHostFD, err := childFD.OpenAt(ctx, flags) 110 if err != nil { 111 log.Infof("failed to open %q from child FD", name) 112 client.CloseFD(ctx, childFD.ID(), true /* flush */) 113 return 0, err 114 } 115 client.CloseFD(ctx, childFD.ID(), false /* flush */) 116 client.CloseFD(ctx, childOpenFD, true /* flush */) 117 return childHostFD, nil 118 }