gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/tools/embeddedbinary/embeddedbinary_template.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 embeddedbinary embeds an external binary and provides a function to 16 // exec it. 17 package embeddedbinary 18 19 import ( 20 "bytes" 21 "compress/flate" 22 _ "embed" 23 "fmt" 24 "io" 25 "os" 26 "path" 27 "runtime" 28 "syscall" 29 30 "golang.org/x/sys/unix" 31 ) 32 33 // BinaryName is the name of the embedded binary. 34 const BinaryName = "embedded.bin.name" 35 36 //go:embed embedded.bin.flate 37 var compressedBinary []byte 38 39 // Options is the set of options to execute the embedded binary. 40 type Options struct { 41 // Argv is the set of arguments to exec with. 42 // `Argv[0]` is the name of the binary as invoked. 43 // If Argv is empty, it will default to a single-element slice, with 44 // `Argv[0]` being the binary name. 45 Argv []string 46 47 // Envv is the set of environment variables to pass to the executed process. 48 Envv []string 49 50 // Files is the set of file descriptors to pass to forked processes. 51 // Only used when forking, not pure exec'ing. 52 Files []uintptr 53 } 54 55 // Bogus import to satisfy the compiler that we are using the flate import, 56 // even when compression is disabled. 57 const _ = flate.NoCompression 58 59 // run decompresses and run the embedded binary with the given arguments. 60 // If fork is true, the binary runs in a separate process, and its PID is 61 // returned. 62 // Otherwise, the binary is exec'd, so the current process stops executing. 63 func run(options Options, fork bool) (int, error) { 64 if len(options.Argv) == 0 { 65 options.Argv = []string{BinaryName} 66 } 67 // The "flate.NewReader" below may be replaced by "io.Reader" when 68 // compression is off. 69 binaryReader := flate.NewReader(bytes.NewReader(compressedBinary)) 70 runtime.LockOSThread() 71 defer runtime.UnlockOSThread() 72 oldMask := unix.Umask(0077) 73 defer unix.Umask(oldMask) 74 tmpDir, err := os.MkdirTemp("", "gvisor.*.tmp") 75 if err != nil { 76 return 0, fmt.Errorf("cannot create temp directory: %w", err) 77 } 78 tmpDirHandle, err := os.Open(tmpDir) 79 if err != nil { 80 return 0, fmt.Errorf("cannot open temp directory: %w", err) 81 } 82 defer tmpDirHandle.Close() 83 binPath := path.Join(tmpDir, BinaryName) 84 tmpFile, err := os.OpenFile(binPath, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0700) 85 if err != nil { 86 return 0, fmt.Errorf("cannot open temp file: %w", err) 87 } 88 if err := os.RemoveAll(tmpDir); err != nil { 89 return 0, fmt.Errorf("cannot remove temp directory: %w", err) 90 } 91 unix.Umask(oldMask) 92 if _, err := io.Copy(tmpFile, binaryReader); err != nil { 93 tmpFile.Close() 94 return 0, fmt.Errorf("cannot decompress embedded binary or write it to temporary file: %w", err) 95 } 96 // Reopen the file for reading. 97 tmpFileReadOnly, err := os.OpenFile(fmt.Sprintf("/proc/self/fd/%d", tmpFile.Fd()), os.O_RDONLY, 0700) 98 if err != nil { 99 tmpFile.Close() 100 return 0, fmt.Errorf("cannot re-open temp file for reading: %w", err) 101 } 102 if err := tmpFile.Close(); err != nil { 103 return 0, fmt.Errorf("cannot close temp file: %w", err) 104 } 105 defer tmpFileReadOnly.Close() 106 tmpFD := tmpFileReadOnly.Fd() 107 if _, err := unix.Seek(int(tmpFD), 0, unix.SEEK_SET); err != nil { 108 return 0, fmt.Errorf("cannot seek temp file back to 0: %w", err) 109 } 110 fdPath := fmt.Sprintf("/proc/self/fd/%d", tmpFD) 111 if fork { 112 return syscall.ForkExec(fdPath, options.Argv, &syscall.ProcAttr{ 113 Env: options.Envv, 114 Files: options.Files, 115 }) 116 } 117 if err := unix.Exec(fdPath, options.Argv, options.Envv); err != nil { 118 return 0, fmt.Errorf("cannot exec embedded binary: %w", err) 119 } 120 panic("unreachable") 121 } 122 123 // Exec execs the embedded binary. The current process is replaced. 124 // This function only returns if unsuccessful. 125 func Exec(options Options) error { 126 _, err := run(options, false) 127 return err 128 } 129 130 // ForkExec runs the embedded binary in a separate process. 131 // Returns the PID of the child process. 132 func ForkExec(options Options) (int, error) { 133 return run(options, true) 134 }