github.com/metacubex/gvisor@v0.0.0-20240320004321-933faba989ec/runsc/donation/donation.go (about) 1 // Copyright 2022 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 donation tracks files that are being donated to a child process and 16 // using flags to notified the child process where the FDs are. 17 package donation 18 19 import ( 20 "fmt" 21 "os" 22 "os/exec" 23 24 "github.com/metacubex/gvisor/pkg/log" 25 "github.com/metacubex/gvisor/runsc/specutils" 26 ) 27 28 // LogDonations logs the FDs we are donating in the command. 29 func LogDonations(cmd *exec.Cmd) { 30 for i, f := range cmd.ExtraFiles { 31 log.Debugf("Donating FD %d: %q", i+3, f.Name()) 32 } 33 } 34 35 // Agency keeps track of files that need to be donated to a child process. 36 type Agency struct { 37 donations []donation 38 closePending []*os.File 39 } 40 41 type donation struct { 42 flag string 43 files []*os.File 44 } 45 46 // Donate sets up the given files to be donated to another process. The FD 47 // in which the new file will appear in the child process is added as a flag to 48 // the child process, e.g. --flag=3. In case the file is nil, -1 is used for the 49 // flag value and no file is donated to the next process. 50 func (f *Agency) Donate(flag string, files ...*os.File) { 51 f.donations = append(f.donations, donation{flag: flag, files: files}) 52 } 53 54 // DonateAndClose does the same as Donate, but takes ownership of the files 55 // passed in. 56 func (f *Agency) DonateAndClose(flag string, files ...*os.File) { 57 f.Donate(flag, files...) 58 f.closePending = append(f.closePending, files...) 59 } 60 61 // OpenAndDonate is similar to DonateAndClose but handles the opening of the 62 // file for convenience. It's a noop, if path is empty. 63 func (f *Agency) OpenAndDonate(flag, path string, flags int) error { 64 if len(path) == 0 { 65 return nil 66 } 67 file, err := os.OpenFile(path, flags, 0644) 68 if err != nil { 69 return err 70 } 71 f.DonateAndClose(flag, file) 72 return nil 73 } 74 75 // DonateDebugLogFile is similar to DonateAndClose but handles the opening of 76 // the file using specutils.DebugLogFile() for convenience. It's a noop, if 77 // path is empty. 78 func (f *Agency) DonateDebugLogFile(flag, logPattern, command, test string) error { 79 if len(logPattern) == 0 { 80 return nil 81 } 82 file, err := specutils.DebugLogFile(logPattern, command, test) 83 if err != nil { 84 return fmt.Errorf("opening debug log file in %q: %v", logPattern, err) 85 } 86 f.DonateAndClose(flag, file) 87 return nil 88 } 89 90 // Transfer sets up all files and flags to cmd. It can be called multiple times 91 // to partially transfer files to cmd. 92 func (f *Agency) Transfer(cmd *exec.Cmd, nextFD int) int { 93 for _, d := range f.donations { 94 for _, file := range d.files { 95 fd := -1 96 if file != nil { 97 cmd.ExtraFiles = append(cmd.ExtraFiles, file) 98 fd = nextFD 99 nextFD++ 100 } 101 cmd.Args = append(cmd.Args, fmt.Sprintf("--%s=%d", d.flag, fd)) 102 } 103 } 104 // Reset donations made so far in case more transfers are needed. 105 f.donations = nil 106 return nextFD 107 } 108 109 // DonateAndTransferCustomFiles sets up the flags for passing file descriptors from the 110 // host to the sandbox. Making use of the agency is not necessary, 111 func DonateAndTransferCustomFiles(cmd *exec.Cmd, nextFD int, files map[int]*os.File) int { 112 for fd, file := range files { 113 cmd.Args = append(cmd.Args, fmt.Sprintf("--pass-fd=%d:%d", nextFD, fd)) 114 cmd.ExtraFiles = append(cmd.ExtraFiles, file) 115 nextFD++ 116 } 117 return nextFD 118 } 119 120 // Close closes any files the agency has taken ownership over. 121 func (f *Agency) Close() { 122 for _, file := range f.closePending { 123 if file != nil { 124 _ = file.Close() 125 } 126 } 127 f.closePending = nil 128 }