github.com/yasker/longhorn-engine@v0.0.0-20160621014712-6ed6cfca0729/frontend/tcmu/main.go (about)

     1  package tcmu
     2  
     3  /*
     4  #cgo LDFLAGS: -ltcmu -lnl-3 -lnl-genl-3 -lm
     5  
     6  #include <errno.h>
     7  #include <stdlib.h>
     8  #include <unistd.h>
     9  #include <scsi/scsi.h>
    10  #include <libtcmu.h>
    11  #include <scsi_defs.h>
    12  
    13  extern struct tcmulib_context *tcmu_init();
    14  extern bool tcmu_poll_master_fd(struct tcmulib_context *cxt);
    15  extern int tcmu_wait_for_next_command(struct tcmu_device *dev);
    16  extern void *allocate_buffer(int length);
    17  
    18  */
    19  import "C"
    20  import (
    21  	"errors"
    22  	"strings"
    23  	"sync"
    24  	"unsafe"
    25  
    26  	"github.com/Sirupsen/logrus"
    27  	"github.com/rancher/longhorn/types"
    28  	"golang.org/x/sys/unix"
    29  )
    30  
    31  var (
    32  	log = logrus.WithFields(logrus.Fields{"pkg": "tcmu"})
    33  
    34  	// this is super dirty
    35  	backend types.ReaderWriterAt
    36  	cxt     *C.struct_tcmulib_context
    37  	volume  string
    38  
    39  	done    chan struct{}
    40  	devFd   int32
    41  	pipeFds []int
    42  )
    43  
    44  type State struct {
    45  	sync.Mutex
    46  
    47  	volume    string
    48  	lbas      int64
    49  	blockSize int
    50  	backend   types.ReaderWriterAt
    51  }
    52  
    53  //export devOpen
    54  func devOpen(dev Device) int {
    55  	state := &State{
    56  		backend: backend,
    57  	}
    58  	blockSizeStr := C.CString("hw_block_size")
    59  	defer C.free(unsafe.Pointer(blockSizeStr))
    60  	blockSize := int(C.tcmu_get_attribute(dev, blockSizeStr))
    61  	if blockSize == -1 {
    62  		log.Errorln("Cannot find valid hw_block_size")
    63  		return -C.EINVAL
    64  	}
    65  	state.blockSize = blockSize
    66  
    67  	size := int64(C.tcmu_get_device_size(dev))
    68  	if size == -1 {
    69  		log.Errorln("Cannot find valid disk size")
    70  		return -C.EINVAL
    71  	}
    72  	state.lbas = size / int64(state.blockSize)
    73  
    74  	cfgString := C.GoString(C.tcmu_get_dev_cfgstring(dev))
    75  	if cfgString == "" {
    76  		log.Errorln("Cannot find configuration string")
    77  		return -C.EINVAL
    78  	}
    79  
    80  	id := strings.TrimPrefix(cfgString, "longhorn//")
    81  	if id != volume {
    82  		log.Debugf("Ignore volume %s, which is not mine", id)
    83  		return -C.EINVAL
    84  	}
    85  	state.volume = id
    86  	devFd = int32(C.tcmu_get_dev_fd(dev))
    87  
    88  	go state.HandleRequest(dev)
    89  
    90  	log.Infof("Device %s added", state.volume)
    91  	return 0
    92  }
    93  
    94  func (s *State) HandleRequest(dev Device) {
    95  	ch := make(chan bool)
    96  	finished := false
    97  	go s.waitForNextCommand(dev, ch)
    98  	for !finished {
    99  		select {
   100  		case <-done:
   101  			log.Errorln("Handle request finished")
   102  			finished = true
   103  		case success := <-ch:
   104  			if !success {
   105  				finished = true
   106  				break
   107  			}
   108  			C.tcmulib_processing_start(dev)
   109  			cmd := C.tcmulib_get_next_command(dev)
   110  			for cmd != nil {
   111  				go s.processCommand(dev, cmd)
   112  				cmd = C.tcmulib_get_next_command(dev)
   113  			}
   114  		}
   115  	}
   116  }
   117  
   118  func (s *State) waitForNextCommand(dev Device, ch chan bool) {
   119  	for {
   120  		pfd := []unix.PollFd{
   121  			{
   122  				Fd:      devFd,
   123  				Events:  unix.POLLIN,
   124  				Revents: 0,
   125  			},
   126  			{
   127  				Fd:      int32(pipeFds[0]),
   128  				Events:  unix.POLLIN,
   129  				Revents: 0,
   130  			},
   131  		}
   132  		_, err := unix.Poll(pfd, -1)
   133  		if err != nil {
   134  			log.Errorln("Poll command failed: ", err)
   135  			ch <- false
   136  			break
   137  		}
   138  		if pfd[1].Revents == unix.POLLIN {
   139  			log.Infoln("Poll command receive finish signal")
   140  			ch <- false
   141  			break
   142  		}
   143  		if pfd[0].Revents != 0 && pfd[0].Revents != unix.POLLIN {
   144  			log.Errorln("Poll received unexpect event: ", pfd[0].Revents)
   145  			ch <- false
   146  			break
   147  		}
   148  		ch <- true
   149  	}
   150  }
   151  
   152  func (s *State) handleReadCommand(dev Device, cmd Command) int {
   153  	offset := CmdGetLba(cmd) * int64(s.blockSize)
   154  	length := CmdGetXferLength(cmd) * s.blockSize
   155  
   156  	buf := make([]byte, length)
   157  	_, err := s.backend.ReadAt(buf, offset)
   158  	if err != nil {
   159  		log.Errorln("read failed: ", err.Error())
   160  		return CmdSetMediumError(cmd)
   161  	}
   162  
   163  	copied := CmdMemcpyIntoIovec(cmd, buf, length)
   164  	if copied != length {
   165  		log.Errorln("read failed: unable to complete buffer copy ")
   166  		return CmdSetMediumError(cmd)
   167  	}
   168  	return C.SAM_STAT_GOOD
   169  }
   170  
   171  func (s *State) handleWriteCommand(dev Device, cmd Command) int {
   172  	offset := CmdGetLba(cmd) * int64(s.blockSize)
   173  	length := CmdGetXferLength(cmd) * s.blockSize
   174  
   175  	buf := make([]byte, length, length)
   176  	if buf == nil {
   177  		log.Errorln("read failed: fail to allocate buffer")
   178  		return CmdSetMediumError(cmd)
   179  	}
   180  	copied := CmdMemcpyFromIovec(cmd, buf, length)
   181  	if copied != length {
   182  		log.Errorln("write failed: unable to complete buffer copy ")
   183  		return CmdSetMediumError(cmd)
   184  	}
   185  
   186  	if _, err := s.backend.WriteAt(buf, offset); err != nil {
   187  		log.Errorln("write failed: ", err.Error())
   188  		return CmdSetMediumError(cmd)
   189  	}
   190  
   191  	return C.SAM_STAT_GOOD
   192  }
   193  
   194  func (s *State) processCommand(dev Device, cmd Command) {
   195  	ret := s.handleCommand(dev, cmd)
   196  
   197  	s.Lock()
   198  	defer s.Unlock()
   199  
   200  	C.tcmulib_command_complete(dev, cmd, C.int(ret))
   201  	C.tcmulib_processing_complete(dev)
   202  }
   203  
   204  func (s *State) handleCommand(dev Device, cmd Command) int {
   205  	scsiCmd := CmdGetScsiCmd(cmd)
   206  	switch scsiCmd {
   207  	case C.INQUIRY:
   208  		return CmdEmulateInquiry(cmd, dev)
   209  	case C.TEST_UNIT_READY:
   210  		return CmdEmulateTestUnitReady(cmd)
   211  	case C.SERVICE_ACTION_IN_16:
   212  		return CmdEmulateServiceActionIn(cmd, s.lbas, s.blockSize)
   213  	case C.MODE_SENSE, C.MODE_SENSE_10:
   214  		return CmdEmulateModeSense(cmd)
   215  	case C.MODE_SELECT, C.MODE_SELECT_10:
   216  		return CmdEmulateModeSelect(cmd)
   217  	case C.READ_6, C.READ_10, C.READ_12, C.READ_16:
   218  		return s.handleReadCommand(dev, cmd)
   219  	case C.WRITE_6, C.WRITE_10, C.WRITE_12, C.WRITE_16:
   220  		return s.handleWriteCommand(dev, cmd)
   221  	default:
   222  		log.Debugf("Ignore unknown SCSI command 0x%x\n", scsiCmd)
   223  	}
   224  	return C.TCMU_NOT_HANDLED
   225  }
   226  
   227  //export devClose
   228  func devClose(dev Device) {
   229  	cfgString := C.GoString(C.tcmu_get_dev_cfgstring(dev))
   230  	if cfgString == "" {
   231  		log.Errorln("Cannot find configuration string")
   232  		return
   233  	}
   234  
   235  	id := strings.TrimPrefix(cfgString, "longhorn//")
   236  	if id != volume {
   237  		//Ignore close other devs
   238  		return
   239  	}
   240  	log.Infof("Device %s removed", volume)
   241  }
   242  
   243  //export devCheckConfig
   244  func devCheckConfig(cfg *C.char, reason **C.char) bool {
   245  	cfgString := C.GoString(cfg)
   246  	if cfgString == "" {
   247  		// Don't want deal with free or cause memory leak, so ignore
   248  		// reason
   249  		log.Errorln("Cannot find valid configuration string")
   250  		return false
   251  	}
   252  
   253  	id := strings.TrimPrefix(cfgString, "longhorn//")
   254  	if id != volume {
   255  		//it's for others
   256  		*reason = C.CString("Not current volume")
   257  		log.Debugf("%s is not my volume", id)
   258  		return false
   259  	}
   260  	return true
   261  }
   262  
   263  func start(name string, rw types.ReaderWriterAt) error {
   264  	if cxt == nil {
   265  		done = make(chan struct{})
   266  		// this is super dirty
   267  		backend = rw
   268  		volume = name
   269  		pipeFds = make([]int, 2)
   270  		if err := unix.Pipe(pipeFds); err != nil {
   271  			return err
   272  		}
   273  		cxt = C.tcmu_init()
   274  		if cxt == nil {
   275  			return errors.New("TCMU ctx is nil")
   276  		}
   277  		// We don't want to poll main fd because devOpen() will be
   278  		// called once for tcmu_init(). We don't need to listen to
   279  		// further events for now.
   280  	}
   281  
   282  	return nil
   283  }
   284  
   285  func stop() {
   286  	if cxt != nil {
   287  		// notify HandleRequest() that we're done
   288  		if _, err := unix.Write(pipeFds[1], []byte{0}); err != nil {
   289  			log.Errorln("Fail to notify poll for finishing: ", err)
   290  		}
   291  		close(done)
   292  		C.tcmulib_close(cxt)
   293  		if err := unix.Close(pipeFds[0]); err != nil {
   294  			log.Errorln("Fail to close pipeFds[0]: ", err)
   295  		}
   296  		if err := unix.Close(pipeFds[1]); err != nil {
   297  			log.Errorln("Fail to close pipeFds[1]: ", err)
   298  		}
   299  		cxt = nil
   300  	}
   301  }