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 }