github.com/kata-containers/runtime@v0.0.0-20210505125100-04f29832a923/virtcontainers/virtiofsd.go (about) 1 // Copyright (c) 2019 Intel Corporation 2 // 3 // SPDX-License-Identifier: Apache-2.0 4 // 5 6 package virtcontainers 7 8 import ( 9 "bufio" 10 "context" 11 "fmt" 12 "io" 13 "net" 14 "os" 15 "os/exec" 16 "path/filepath" 17 "strings" 18 "syscall" 19 "time" 20 21 "github.com/kata-containers/runtime/virtcontainers/utils" 22 opentracing "github.com/opentracing/opentracing-go" 23 "github.com/pkg/errors" 24 log "github.com/sirupsen/logrus" 25 ) 26 27 const ( 28 //Timeout to wait in secounds 29 virtiofsdStartTimeout = 5 30 ) 31 32 type Virtiofsd interface { 33 // Start virtiofsd, return pid of virtiofsd process 34 Start(context.Context) (pid int, err error) 35 // Stop virtiofsd process 36 Stop() error 37 } 38 39 // Helper function to check virtiofsd is serving 40 type virtiofsdWaitFunc func(runningCmd *exec.Cmd, stderr io.ReadCloser, debug bool) error 41 42 type virtiofsd struct { 43 // path to virtiofsd daemon 44 path string 45 // socketPath where daemon will serve 46 socketPath string 47 // cache size for virtiofsd 48 cache string 49 // extraArgs list of extra args to append to virtiofsd command 50 extraArgs []string 51 // sourcePath path that daemon will help to share 52 sourcePath string 53 // debug flag 54 debug bool 55 // PID process ID of virtiosd process 56 PID int 57 // Neded by tracing 58 ctx context.Context 59 // wait helper function to check if virtiofsd is serving 60 wait virtiofsdWaitFunc 61 } 62 63 // Open socket on behalf of virtiofsd 64 // return file descriptor to be used by virtiofsd. 65 func (v *virtiofsd) getSocketFD() (*os.File, error) { 66 var listener *net.UnixListener 67 68 if _, err := os.Stat(filepath.Dir(v.socketPath)); err != nil { 69 return nil, errors.Errorf("Socket directory does not exist %s", filepath.Dir(v.socketPath)) 70 } 71 72 listener, err := net.ListenUnix("unix", &net.UnixAddr{Name: v.socketPath, Net: "unix"}) 73 if err != nil { 74 return nil, err 75 } 76 defer listener.Close() 77 78 listener.SetUnlinkOnClose(false) 79 80 return listener.File() 81 } 82 83 // Start the virtiofsd daemon 84 func (v *virtiofsd) Start(ctx context.Context) (int, error) { 85 span, _ := v.trace("Start") 86 defer span.Finish() 87 pid := 0 88 89 if err := v.valid(); err != nil { 90 return pid, err 91 } 92 93 cmd := exec.Command(v.path) 94 95 socketFD, err := v.getSocketFD() 96 if err != nil { 97 return 0, err 98 } 99 100 cmd.ExtraFiles = append(cmd.ExtraFiles, socketFD) 101 102 // Extra files start from 2 (0: stdin, 1: stdout, 2: stderr) 103 // Extra FDs for virtiofsd start from 3 104 // Get the FD number for previous added socketFD 105 socketFdNumber := 2 + uint(len(cmd.ExtraFiles)) 106 args, err := v.args(socketFdNumber) 107 if err != nil { 108 return pid, err 109 } 110 cmd.Args = append(cmd.Args, args...) 111 112 v.Logger().WithField("path", v.path).Info() 113 v.Logger().WithField("args", strings.Join(args, " ")).Info() 114 115 if err = utils.StartCmd(cmd); err != nil { 116 return pid, err 117 } 118 119 defer func() { 120 if err != nil { 121 cmd.Process.Kill() 122 } 123 }() 124 125 if v.wait == nil { 126 v.wait = waitVirtiofsReady 127 } 128 129 return pid, socketFD.Close() 130 } 131 132 func (v *virtiofsd) Stop() error { 133 if err := v.kill(); err != nil { 134 return nil 135 } 136 137 if v.socketPath == "" { 138 return errors.New("vitiofsd socket path is empty") 139 } 140 141 err := os.Remove(v.socketPath) 142 if err != nil { 143 v.Logger().WithError(err).WithField("path", v.socketPath).Warn("removing virtiofsd socket failed") 144 } 145 return nil 146 } 147 148 func (v *virtiofsd) args(FdSocketNumber uint) ([]string, error) { 149 if v.sourcePath == "" { 150 return []string{}, errors.New("vitiofsd source path is empty") 151 } 152 153 if _, err := os.Stat(v.sourcePath); os.IsNotExist(err) { 154 return nil, err 155 } 156 157 args := []string{ 158 // Send logs to syslog 159 "--syslog", 160 // foreground operation 161 "-f", 162 // cache mode for virtiofsd 163 "-o", "cache=" + v.cache, 164 // disable posix locking in daemon: bunch of basic posix locks properties are broken 165 // apt-get update is broken if enabled 166 "-o", "no_posix_lock", 167 // shared directory tree 168 "-o", "source=" + v.sourcePath, 169 // fd number of vhost-user socket 170 fmt.Sprintf("--fd=%v", FdSocketNumber), 171 } 172 173 if v.debug { 174 args = append(args, "-o", "debug") 175 } 176 177 if len(v.extraArgs) != 0 { 178 args = append(args, v.extraArgs...) 179 } 180 181 return args, nil 182 } 183 184 func (v *virtiofsd) valid() error { 185 if v.path == "" { 186 errors.New("virtiofsd path is empty") 187 } 188 189 if v.socketPath == "" { 190 errors.New("Virtiofsd socket path is empty") 191 } 192 193 if v.sourcePath == "" { 194 errors.New("virtiofsd source path is empty") 195 196 } 197 198 return nil 199 } 200 201 func (v *virtiofsd) Logger() *log.Entry { 202 return virtLog.WithField("subsystem", "virtiofsd") 203 } 204 205 func (v *virtiofsd) trace(name string) (opentracing.Span, context.Context) { 206 if v.ctx == nil { 207 v.ctx = context.Background() 208 } 209 210 span, ctx := opentracing.StartSpanFromContext(v.ctx, name) 211 212 span.SetTag("subsystem", "virtiofds") 213 214 return span, ctx 215 } 216 217 func waitVirtiofsReady(cmd *exec.Cmd, stderr io.ReadCloser, debug bool) error { 218 if cmd == nil { 219 return errors.New("cmd is nil") 220 } 221 222 sockReady := make(chan error, 1) 223 go func() { 224 scanner := bufio.NewScanner(stderr) 225 var sent bool 226 for scanner.Scan() { 227 if debug { 228 virtLog.WithField("source", "virtiofsd").Debug(scanner.Text()) 229 } 230 if !sent && strings.Contains(scanner.Text(), "Waiting for vhost-user socket connection...") { 231 sockReady <- nil 232 sent = true 233 } 234 235 } 236 if !sent { 237 if err := scanner.Err(); err != nil { 238 sockReady <- err 239 240 } else { 241 sockReady <- fmt.Errorf("virtiofsd did not announce socket connection") 242 243 } 244 245 } 246 // Wait to release resources of virtiofsd process 247 cmd.Process.Wait() 248 }() 249 250 var err error 251 select { 252 case err = <-sockReady: 253 case <-time.After(virtiofsdStartTimeout * time.Second): 254 err = fmt.Errorf("timed out waiting for vitiofsd ready mesage pid=%d", cmd.Process.Pid) 255 } 256 257 return err 258 } 259 260 func (v *virtiofsd) kill() (err error) { 261 span, _ := v.trace("kill") 262 defer span.Finish() 263 264 if v.PID == 0 { 265 return errors.New("invalid virtiofsd PID(0)") 266 } 267 268 err = syscall.Kill(v.PID, syscall.SIGKILL) 269 if err != nil { 270 v.PID = 0 271 } 272 273 return err 274 } 275 276 // virtiofsdMock mock implementation for unit test 277 type virtiofsdMock struct { 278 } 279 280 // Start the virtiofsd daemon 281 func (v *virtiofsdMock) Start(ctx context.Context) (int, error) { 282 return 9999999, nil 283 } 284 285 func (v *virtiofsdMock) Stop() error { 286 return nil 287 }