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  }