#!/usr/bin/python # -*- coding: utf-8 -*- """ Auxiliary script used to send data between ports on guests. @copyright: 2010 Red Hat, Inc. @author: Jiri Zupka (jzupka@redhat.com) @author: Lukas Doktor (ldoktor@redhat.com) """ import threading from threading import Thread import os, select, re, random, sys, array, stat import fcntl, traceback, signal, time DEBUGPATH = "/sys/kernel/debug" SYSFSPATH = "/sys/class/virtio-ports/" DEVPATH = "/dev/virtio-ports/" exiting = False class VirtioGuest: """ Test tools of virtio_ports. """ LOOP_NONE = 0 LOOP_POLL = 1 LOOP_SELECT = 2 def __init__(self): self.files = {} self.exit_thread = threading.Event() self.threads = [] self.ports = {} self.poll_fds = {} self.catch_signal = None self.use_config = threading.Event() 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, in_files=None): """ Get info about ports from kernel debugfs. @param in_files: Array of input files. @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)) if in_files is not None: dev_names = os.listdir('/dev') rep = re.compile(r"vport[0-9]p[0-9]+") dev_names = filter(lambda x: rep.match(x) is not None, dev_names) if len(dev_names) != len(in_files): print ("FAIL: Not all ports were successfully initialized " "in /dev, only %d from %d." % (len(dev_names), len(in_files))) return if len(viop_names) != len(in_files): print ("FAIL: Not all ports were successfuly initialized " "in debugfs, only %d from %d." % (len(viop_names), len(in_files))) return for name in viop_names: open_db_file = "%s/virtio-ports/%s" % (DEBUGPATH, name) f = open(open_db_file, 'r') port = {} file = [] for line in iter(f): file.append(line) try: for line in file: 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 does not match " "\n%s - %s\n%s - %s" % (info_name , port_name, "%s/virtio-ports/%s" % (DEBUGPATH, name), port["name"])) dev_ppath = DEVPATH + port_name if not os.path.exists(dev_ppath): print "FAIL: Symlink %s does not exist." % dev_ppath if not os.path.realpath(dev_ppath) != "/dev/name": print "FAIL: Symlink %s is not correct." % dev_ppath except AttributeError: print ("Bad data on file %s:\n%s. " % (open_db_file, "".join(file).strip())) print "FAIL: Bad data on file %s." % open_db_file return ports[port['name']] = port f.close() return ports def check_zero_sym(self): """ Check if port /dev/vport0p0 was created. """ symlink = "/dev/vport0p0" if os.path.exists(symlink): print "PASS: Symlink %s exists." % symlink else: print "FAIL: Symlink %s does not exist." % symlink def init(self, in_files): """ Init and check port properties. """ self.ports = self._get_port_status(in_files) if self.ports is None: return 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" return 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, name="Switch") 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 == VirtioGuest.LOOP_POLL): self._poll_mode() elif (self.method == VirtioGuest.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, name="Sender") 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, inst: print "FAIL: Failed to open file %s" % (name) raise inst return f @staticmethod def pollmask_to_str(mask): """ Conver pool mast to string @param mask: poll return mask """ str = "" if (mask & select.POLLIN): str += "IN " if (mask & select.POLLPRI): str += "PRI IN " if (mask & select.POLLOUT): str += "OUT " if (mask & select.POLLERR): str += "ERR " if (mask & select.POLLHUP): str += "HUP " if (mask & select.POLLMSG): str += "MSG " return str 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) maskstr = VirtioGuest.pollmask_to_str(mask[0][1]) if (mask[0][1] & expected) == expected: print "PASS: Events: " + maskstr else: emaskstr = VirtioGuest.pollmask_to_str(expected) print "FAIL: Events: " + maskstr + " Expected: " + emaskstr def lseek(self, port, pos, how): """ Use lseek on the device. The device is unseekable so PASS is returned when lseek command fails and vice versa. @param port: Name of the port @param pos: Offset @param how: Relativ offset os.SEEK_{SET,CUR,END} """ fd = self._open([port])[0] try: os.lseek(fd, pos, how) except Exception, inst: if inst.errno == 29: print "PASS: the lseek failed as expected" else: print inst print "FAIL: unknown error" else: print "FAIL: the lseek unexpectedly passed" 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 """ fd = self._open([port])[0] 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, inst: print "FAIL: Setting (non)blocking mode: " + str(inst) return if mode: print "PASS: set to blocking mode" else: print "PASS: set to nonblocking mode" def __call__(self, sig, frame): """ Call function. Used for signal handle. """ if (sig == signal.SIGIO): self.sigio_handler(sig, frame) def sigio_handler(self, sig, frame): """ Handler for sigio operation. @param sig: signal which call handler. @param frame: frame of caller """ if self.poll_fds: p = select.poll() map(p.register, self.poll_fds.keys()) masks = p.poll(1) print masks for mask in masks: self.poll_fds[mask[0]][1] |= mask[1] def get_sigio_poll_return(self, port): """ Return PASS, FAIL and poll walue in string format. @param port: Port to check poll information. """ fd = self._open([port])[0] maskstr = VirtioGuest.pollmask_to_str(self.poll_fds[fd][1]) if (self.poll_fds[fd][0] ^ self.poll_fds[fd][1]): emaskstr = VirtioGuest.pollmask_to_str(self.poll_fds[fd][0]) print "FAIL: Events: " + maskstr + " Expected: " + emaskstr else: print "PASS: Events: " + maskstr self.poll_fds[fd][1] = 0 def set_pool_want_return(self, port, poll_value): """ Set value to static variable. @param port: Port which should be set excepted mask @param poll_value: Value to check sigio signal. """ fd = self._open([port])[0] self.poll_fds[fd] = [poll_value, 0] print "PASS: Events: " + VirtioGuest.pollmask_to_str(poll_value) def catching_signal(self): """ return: True if should set catch signal, False if ignore signal and none when configuration is not changed. """ ret = self.catch_signal self.catch_signal = None return ret def async(self, port, mode=True, exp_val=0): """ Set port function mode async/sync. @param port: port which should be pooled. @param mode: False to set sync mode, True for sync mode. @param exp_val: Value which should be pooled. """ fd = self._open([port])[0] try: fcntl.fcntl(fd, fcntl.F_SETOWN, os.getpid()) fl = fcntl.fcntl(fd, fcntl.F_GETFL) self.use_config.clear() if mode: fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_ASYNC) self.poll_fds[fd] = [exp_val, 0] self.catch_signal = True else: del self.poll_fds[fd] fcntl.fcntl(fd, fcntl.F_SETFL, fl & ~os.O_ASYNC) self.catch_signal = False os.kill(os.getpid(), signal.SIGUSR1) self.use_config.wait() except Exception, inst: print "FAIL: Setting (a)sync mode: " + str(inst) return if mode: print "PASS: Set to async mode" else: print "PASS: Set to sync mode" def close(self, file): """ Close open port. @param file: File to close. """ descriptor = None path = self.ports[file]["path"] if path is not None: if path in self.files.keys(): descriptor = self.files[path] del self.files[path] if descriptor is not None: try: os.close(descriptor) except Exception, inst: print "FAIL: Closing the file: " + str(inst) return print "PASS: Close" def open(self, in_file): """ Direct open devices. @param in_file: Array of files. @return: Array of descriptors. """ name = self.ports[in_file]["path"] try: self.files[name] = os.open(name, os.O_RDWR) if (self.ports[in_file]["is_console"] == "yes"): print os.system("stty -F %s raw -echo" % (name)) print "PASS: Open all filles correctly." except Exception, 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. @param mode: Mode of switch. """ 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, is_static=False): """ 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 = "" writes = 0 if not is_static: while len(data) < length: data += "%c" % random.randrange(255) try: writes = os.write(in_f[0], data) except Exception, inst: print inst else: while len(data) < 4096: data += "%c" % random.randrange(255) if mode: while (writes < length): try: writes += os.write(in_f[0], data) except Exception, 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, inst: print inst if mode: while (len(recvs) < length): try: recvs += os.read(in_f[0], buffer) except Exception, 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 clean_port(self, port, buffer=1024): in_f = self._open([port]) ret = select.select([in_f[0]], [], [], 1.0) buf = "" if ret[0]: buf = os.read(in_f[0], buffer) print ("PASS: Rest in socket: ") + str(buf[:10]) def is_alive(): """ Check is only main thread is alive and if guest react. """ if threading.activeCount() == 2: print ("PASS: Guest is ok no thread alive") else: threads = "" for thread in threading.enumerate(): threads += thread.name + ", " print ("FAIL: On guest run thread. Active thread:" + threads) def compile(): """ Compile virtio_console_guest.py to speed up. """ import py_compile py_compile.compile(sys.path[0] + "/virtio_console_guest.py") print "PASS: compile" sys.exit() def guest_exit(): global exiting exiting = True def worker(virt): """ Worker thread (infinite) loop of virtio_guest. """ global exiting print "PASS: Daemon start." p = select.poll() p.register(sys.stdin.fileno()) while not exiting: d = p.poll() if (d[0][1] == select.POLLIN): str = raw_input() try: exec str except: exc_type, exc_value, exc_traceback = sys.exc_info() print "On Guest exception from: \n" + "".join( traceback.format_exception(exc_type, exc_value, exc_traceback)) print "FAIL: Guest command exception." elif (d[0][1] & select.POLLHUP): time.sleep(0.5) def sigusr_handler(sig, frame): pass class Daemon: """ Daemonize guest """ def __init__(self, stdin, stdout, stderr): """ Init daemon. @param stdin: path to stdin file. @param stdout: path to stdout file. @param stderr: path to stderr file. """ self.stdin = stdin self.stdout = stdout self.stderr = stderr @staticmethod def is_file_open(path): """ Determine process which open file. @param path: Path to file. @return [[pid,mode], ... ]. """ opens = [] pids = os.listdir('/proc') for pid in sorted(pids): try: int(pid) except ValueError: continue fd_dir = os.path.join('/proc', pid, 'fd') try: for file in os.listdir(fd_dir): try: p = os.path.join(fd_dir, file) link = os.readlink(os.path.join(fd_dir, file)) if link == path: mode = os.lstat(p).st_mode opens.append([pid, mode]) except OSError: continue except OSError, e: if e.errno == 2: continue raise return opens def daemonize(self): """ Run guest as a daemon. """ try: pid = os.fork() if pid > 0: return False except OSError, e: sys.stderr.write("Daemonize failed: %s\n" % (e)) sys.exit(1) os.chdir("/") os.setsid() os.umask(0) try: pid = os.fork() if pid > 0: sys.exit(0) except OSError, e: sys.stderr.write("Daemonize failed: %s\n" % (e)) sys.exit(1) sys.stdout.flush() sys.stderr.flush() si = file(self.stdin,'r') so = file(self.stdout,'w') se = file(self.stderr,'w') os.dup2(si.fileno(), sys.stdin.fileno()) os.dup2(so.fileno(), sys.stdout.fileno()) os.dup2(se.fileno(), sys.stderr.fileno()) sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0) sys.stderr = os.fdopen(sys.stderr.fileno(), 'w', 0) return True def start(self): """ Start the daemon @return: PID of daemon. """ # Check for a pidfile to see if the daemon already runs openers = self.is_file_open(self.stdout) rundaemon = False if len(openers) > 0: for i in openers: if i[1] & stat.S_IWUSR: rundaemon = True openers.remove(i) if len(openers) > 0: for i in openers: os.kill(int(i[0]), 9) time.sleep(0.3) # Start the daemon if not rundaemon: if self.daemonize(): self.run() def run(self): """ Run guest main thread """ global exiting virt = VirtioGuest() slave = Thread(target=worker, args=(virt, )) slave.start() signal.signal(signal.SIGUSR1, sigusr_handler) signal.signal(signal.SIGALRM, sigusr_handler) while not exiting: signal.alarm(1) signal.pause() catch = virt.catching_signal() if catch: signal.signal(signal.SIGIO, virt) elif catch is False: signal.signal(signal.SIGIO, signal.SIG_DFL) if catch is not None: virt.use_config.set() print "PASS: guest_exit" sys.exit(0) def main(): """ Main function with infinite loop to catch signal from system. """ if (len(sys.argv) > 1) and (sys.argv[1] == "-c"): compile() stdin = "/tmp/guest_daemon_pi" stdout = "/tmp/guest_daemon_po" stderr = "/tmp/guest_daemon_pe" for f in [stdin, stdout, stderr]: try: os.mkfifo(f) except OSError, e: if e.errno == 17: pass daemon = Daemon(stdin, stdout, stderr) daemon.start() d_stdin = os.open(stdin, os.O_WRONLY) d_stdout = os.open(stdout, os.O_RDONLY) d_stderr = os.open(stderr, os.O_RDONLY) s_stdin = sys.stdin.fileno() s_stdout = sys.stdout.fileno() s_stderr = sys.stderr.fileno() pid = filter(lambda x: x[0] != str(os.getpid()), daemon.is_file_open(stdout))[0][0] print "PASS: Start" while 1: ret = select.select([d_stderr, d_stdout, s_stdin], [], [], 1.0) if s_stdin in ret[0]: os.write(d_stdin,os.read(s_stdin, 1)) if d_stdout in ret[0]: os.write(s_stdout,os.read(d_stdout, 1024)) if d_stderr in ret[0]: os.write(s_stderr,os.read(d_stderr, 1024)) if not os.path.exists("/proc/" + pid): sys.exit(0) os.close(d_stdin) os.close(d_stdout) os.close(d_stderr) if __name__ == "__main__": main()