| #!/usr/bin/python |
| # -*- coding: utf-8 -*- |
| """ |
| Auxiliary script used to send data between ports on guests. |
| |
| @copyright: 2008-2009 Red Hat Inc. |
| @author: Jiri Zupka (jzupka@redhat.com) |
| @author: Lukas Doktor (ldoktor@redhat.com) |
| """ |
| #from _pydev_SimpleXMLRPCServer import fcntl |
| |
| """ |
| TODO: |
| virt.init([consoles]) # sysfs, udev, OK |
| virt.open(name) |
| virt.close(name) |
| virt.poll(name, eventmask, timeout) # poll.register(), poll.poll(), |
| return event |
| virt.send(name, length) # host disconnected |
| virt.recv(name, length) # host disconnected |
| virt.blocking(name, true) # true = blocking, false = nonblocking |
| virt.loopback(in_names, out_names, type="None") # use select/poll |
| """ |
| |
| import threading |
| from threading import Thread |
| import os, time, select, re, random, sys, array, fcntl, array, subprocess |
| |
| DEBUGPATH = "/sys/kernel/debug" |
| SYSFSPATH = "/sys/class/virtio-ports/" |
| |
| |
| class virtio_guest(): |
| |
| LOOP_NONE = 0 |
| LOOP_POLL = 1 |
| LOOP_SELECT = 2 |
| |
| def __init__(self): |
| self.files = {} |
| self.exit_thread = threading.Event() |
| self.threads = [] |
| self.ports = {} |
| |
| |
| def _readfile(self, name): |
| """ |
| Read file and return content as string |
| |
| @param name: Name of file |
| @return: Content of file as string |
| """ |
| out = "" |
| try: |
| f = open(name, "r") |
| out = f.read() |
| f.close() |
| except: |
| print "FAIL: Cannot open file %s" % (name) |
| |
| return out |
| |
| |
| def _get_port_status(self): |
| """ |
| Get info about ports from kernel debugfs. |
| |
| @return: Ports dictionary of port properties |
| """ |
| ports = {} |
| not_present_msg = "FAIL: There's no virtio-ports dir in debugfs" |
| if (not os.path.ismount(DEBUGPATH)): |
| os.system('mount -t debugfs none %s' % (DEBUGPATH)) |
| try: |
| if not os.path.isdir('%s/virtio-ports' % (DEBUGPATH)): |
| print not_present_msg |
| except: |
| print not_present_msg |
| else: |
| viop_names = os.listdir('%s/virtio-ports' % (DEBUGPATH)) |
| for name in viop_names: |
| f = open("%s/virtio-ports/%s" % (DEBUGPATH, name), 'r') |
| port = {} |
| for line in iter(f): |
| m = re.match("(\S+): (\S+)", line) |
| port[m.group(1)] = m.group(2) |
| |
| if (port['is_console'] == "yes"): |
| port["path"] = "/dev/hvc%s" % (port["console_vtermno"]) |
| # Console works like a serialport |
| else: |
| port["path"] = "/dev/%s" % name |
| |
| if (not os.path.exists(port['path'])): |
| print "FAIL: %s not exist" % port['path'] |
| |
| sysfspath = SYSFSPATH + name |
| if (not os.path.isdir(sysfspath)): |
| print "FAIL: %s not exist" % (sysfspath) |
| |
| info_name = sysfspath + "/name" |
| port_name = self._readfile(info_name).strip() |
| if (port_name != port["name"]): |
| print ("FAIL: Port info not match \n%s - %s\n%s - %s" % |
| (info_name , port_name, |
| "%s/virtio-ports/%s" % (DEBUGPATH, name), |
| port["name"])) |
| |
| ports[port['name']] = port |
| f.close() |
| |
| return ports |
| |
| |
| def init(self, in_files): |
| """ |
| Init and check port properties. |
| """ |
| self.ports = self._get_port_status() |
| |
| for item in in_files: |
| if (item[1] != self.ports[item[0]]["is_console"]): |
| print self.ports |
| print "FAIL: Host console is not like console on guest side\n" |
| print "PASS: Init and check virtioconsole files in system." |
| |
| |
| class switch(Thread): |
| """ |
| Thread that sends data between ports. |
| """ |
| def __init__ (self, in_files, out_files, event, |
| cachesize=1024, method=0): |
| """ |
| @param in_files: Array of input files. |
| @param out_files: Array of output files. |
| @param method: Method of read/write access. |
| @param cachesize: Block to receive and send. |
| """ |
| Thread.__init__(self) |
| |
| self.in_files = in_files |
| self.out_files = out_files |
| self.exit_thread = event |
| self.method = method |
| |
| self.cachesize = cachesize |
| |
| |
| def _none_mode(self): |
| """ |
| Read and write to device in blocking mode |
| """ |
| data = "" |
| while not self.exit_thread.isSet(): |
| data = "" |
| for desc in self.in_files: |
| data += os.read(desc, self.cachesize) |
| if data != "": |
| for desc in self.out_files: |
| os.write(desc, data) |
| |
| |
| def _poll_mode(self): |
| """ |
| Read and write to device in polling mode. |
| """ |
| |
| pi = select.poll() |
| po = select.poll() |
| |
| for fd in self.in_files: |
| pi.register(fd, select.POLLIN) |
| |
| for fd in self.out_files: |
| po.register(fd, select.POLLOUT) |
| |
| while not self.exit_thread.isSet(): |
| data = "" |
| t_out = self.out_files |
| |
| readyf = pi.poll(1.0) |
| for i in readyf: |
| data += os.read(i[0], self.cachesize) |
| |
| if data != "": |
| while ((len(t_out) != len(readyf)) and not |
| self.exit_thread.isSet()): |
| readyf = po.poll(1.0) |
| for desc in t_out: |
| os.write(desc, data) |
| |
| |
| def _select_mode(self): |
| """ |
| Read and write to device in selecting mode. |
| """ |
| while not self.exit_thread.isSet(): |
| ret = select.select(self.in_files, [], [], 1.0) |
| data = "" |
| if ret[0] != []: |
| for desc in ret[0]: |
| data += os.read(desc, self.cachesize) |
| if data != "": |
| ret = select.select([], self.out_files, [], 1.0) |
| while ((len(self.out_files) != len(ret[1])) and not |
| self.exit_thread.isSet()): |
| ret = select.select([], self.out_files, [], 1.0) |
| for desc in ret[1]: |
| os.write(desc, data) |
| |
| |
| def run(self): |
| if (self.method == virtio_guest.LOOP_POLL): |
| self._poll_mode() |
| elif (self.method == virtio_guest.LOOP_SELECT): |
| self._select_mode() |
| else: |
| self._none_mode() |
| |
| |
| class sender(Thread): |
| """ |
| Creates a thread which sends random blocks of data to dst port. |
| """ |
| def __init__(self, port, event, length): |
| """ |
| @param port: Destination port |
| @param length: Length of the random data block |
| """ |
| Thread.__init__(self) |
| self.port = port |
| self.exit_thread = event |
| self.data = array.array('L') |
| for i in range(max(length / self.data.itemsize, 1)): |
| self.data.append(random.randrange(sys.maxint)) |
| |
| def run(self): |
| while not self.exit_thread.isSet(): |
| os.write(self.port, self.data) |
| |
| |
| def _open(self, in_files): |
| """ |
| Open devices and return array of descriptors |
| |
| @param in_files: Files array |
| @return: Array of descriptor |
| """ |
| f = [] |
| |
| for item in in_files: |
| name = self.ports[item]["path"] |
| if (name in self.files): |
| f.append(self.files[name]) |
| else: |
| try: |
| self.files[name] = os.open(name, os.O_RDWR) |
| if (self.ports[item]["is_console"] == "yes"): |
| print os.system("stty -F %s raw -echo" % (name)) |
| print os.system("stty -F %s -a" % (name)) |
| f.append(self.files[name]) |
| except Exception as inst: |
| print "FAIL: Failed to open file %s" % (name) |
| raise inst |
| return f |
| |
| |
| def poll(self, port, expected, timeout=500): |
| """ |
| Pool event from device and print event like text. |
| |
| @param file: Device. |
| """ |
| in_f = self._open([port]) |
| |
| p = select.poll() |
| p.register(in_f[0]) |
| |
| mask = p.poll(timeout) |
| |
| str = "" |
| if (mask[0][1] & select.POLLIN): |
| str += "IN " |
| if (mask[0][1] & select.POLLPRI): |
| str += "PRI IN " |
| if (mask[0][1] & select.POLLOUT): |
| str += "OUT " |
| if (mask[0][1] & select.POLLERR): |
| str += "ERR " |
| if (mask[0][1] & select.POLLHUP): |
| str += "HUP " |
| if (mask[0][1] & select.POLLMSG): |
| str += "MSG " |
| |
| if (mask[0][1] & expected) == expected: |
| print "PASS: Events: " + str |
| else: |
| print "FAIL: Events: " + str |
| |
| |
| def blocking(self, port, mode=False): |
| """ |
| Set port function mode blocking/nonblocking |
| |
| @param port: port to set mode |
| @param mode: False to set nonblock mode, True for block mode |
| """ |
| path = self.ports[port]["path"] |
| fd = self.files[path] |
| |
| try: |
| fl = fcntl.fcntl(fd, fcntl.F_GETFL) |
| if not mode: |
| fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK) |
| else: |
| fcntl.fcntl(fd, fcntl.F_SETFL, fl & ~os.O_NONBLOCK) |
| |
| except Exception as inst: |
| print "FAIL: Setting (non)blocking mode: " + str(inst) |
| return |
| |
| print ("PASS: set blocking mode to %s mode" % |
| ("blocking" if mode else "nonblocking")) |
| |
| |
| def close(self, file): |
| """ |
| Close open port. |
| |
| @param file: File to close. |
| """ |
| descriptor = None |
| path = self.ports[file]["path"] |
| if path != None: |
| if path in self.files.keys(): |
| descriptor = self.files[path] |
| del self.files[path] |
| try: |
| os.close(descriptor) |
| except Exception as inst: |
| print "FAIL: Closing the file: " + str(inst) |
| return |
| print "PASS: Close" |
| |
| |
| def open(self, in_files): |
| """ |
| Direct open devices. |
| |
| @param in_files: Array of files. |
| @return: Array of descriptors. |
| """ |
| name = self.ports[in_files]["path"] |
| try: |
| self.files[name] = os.open(name, os.O_RDWR) |
| print "PASS: Open all filles correctly." |
| except Exception as inst: |
| print "%s\nFAIL: Failed open file %s" % (str(inst), name) |
| |
| |
| def loopback(self, in_files, out_files, cachesize=1024, mode=LOOP_NONE): |
| """ |
| Start a switch thread. |
| |
| (There is a problem with multiple opens of a single file). |
| |
| @param in_files: Array of input files. |
| @param out_files: Array of output files. |
| @param cachesize: Cachesize. |
| """ |
| self.ports = self._get_port_status() |
| |
| in_f = self._open(in_files) |
| out_f = self._open(out_files) |
| |
| s = self.switch(in_f, out_f, self.exit_thread, cachesize, mode) |
| s.start() |
| self.threads.append(s) |
| print "PASS: Start switch" |
| |
| |
| def exit_threads(self): |
| """ |
| Function end all running data switch. |
| """ |
| self.exit_thread.set() |
| for th in self.threads: |
| print "join" |
| th.join() |
| self.exit_thread.clear() |
| |
| del self.threads[:] |
| for desc in self.files.itervalues(): |
| os.close(desc) |
| self.files.clear() |
| print "PASS: All threads finished." |
| |
| |
| def die(self): |
| """ |
| Quit consoleswitch. |
| """ |
| self.exit_threads() |
| exit() |
| |
| |
| def send_loop_init(self, port, length): |
| """ |
| Prepares the sender thread. Requires clean thread structure. |
| """ |
| self.ports = self._get_port_status() |
| in_f = self._open([port]) |
| |
| self.threads.append(self.sender(in_f[0], self.exit_thread, length)) |
| print "PASS: Sender prepare" |
| |
| |
| def send_loop(self): |
| """ |
| Start sender data transfer. Requires senderprepare run first. |
| """ |
| self.threads[0].start() |
| print "PASS: Sender start" |
| |
| |
| def send(self, port, length=1, mode=True): |
| """ |
| Send a data of some length |
| |
| @param port: Port to write data |
| @param length: Length of data |
| @param mode: True = loop mode, False = one shoot mode |
| """ |
| in_f = self._open([port]) |
| |
| data = "" |
| while len(data) < length: |
| data += "%c" % random.randrange(255) |
| try: |
| writes = os.write(in_f[0], data) |
| except Exception as inst: |
| print inst |
| if not writes: |
| writes = 0 |
| if mode: |
| while (writes < length): |
| try: |
| writes += os.write(in_f[0], data) |
| except Exception as inst: |
| print inst |
| if writes >= length: |
| print "PASS: Send data length %d" % writes |
| else: |
| print ("FAIL: Partial send: desired %d, transfered %d" % |
| (length, writes)) |
| |
| |
| def recv(self, port, length=1, buffer=1024, mode=True): |
| """ |
| Recv a data of some length |
| |
| @param port: Port to write data |
| @param length: Length of data |
| @param mode: True = loop mode, False = one shoot mode |
| """ |
| in_f = self._open([port]) |
| |
| recvs = "" |
| try: |
| recvs = os.read(in_f[0], buffer) |
| except Exception as inst: |
| print inst |
| if mode: |
| while (len(recvs) < length): |
| try: |
| recvs += os.read(in_f[0], buffer) |
| except Exception as inst: |
| print inst |
| if len(recvs) >= length: |
| print "PASS: Recv data length %d" % len(recvs) |
| else: |
| print ("FAIL: Partial recv: desired %d, transfered %d" % |
| (length, len(recvs))) |
| |
| |
| def compile(): |
| """ |
| Compile virtio_guest.py to speed up. |
| """ |
| import py_compile |
| py_compile.compile(sys.path[0] + "/virtio_guest.py") |
| print "PASS: compile" |
| exit(0) |
| |
| |
| def main(): |
| """ |
| Main (infinite) loop of virtio_guest. |
| """ |
| if (len(sys.argv) > 1) and (sys.argv[1] == "-c"): |
| compile() |
| |
| virt = virtio_guest() |
| print "PASS: Start" |
| |
| while True: |
| str = raw_input() |
| exec str |
| |
| |
| if __name__ == "__main__": |
| main() |