blob: 6362856f26beefa96008518a4ae4c07d63a06c55 [file] [log] [blame]
lmrbbc9dd52009-07-22 20:33:47 +00001#!/usr/bin/python
2import sys, subprocess, pty, select, os, time, signal, re, termios, fcntl
3import threading, logging, commands
4import common, kvm_utils
5
6"""
7A class and functions used for running and controlling child processes.
8
9@copyright: 2008-2009 Red Hat Inc.
10"""
11
12
13def run_bg(command, termination_func=None, output_func=None, output_prefix="",
14 timeout=1.0):
15 """
16 Run command as a subprocess. Call output_func with each line of output
17 from the subprocess (prefixed by output_prefix). Call termination_func
18 when the subprocess terminates. Return when timeout expires or when the
19 subprocess exits -- whichever occurs first.
20
21 @brief: Run a subprocess in the background and collect its output and
22 exit status.
23
24 @param command: The shell command to execute
25 @param termination_func: A function to call when the process terminates
26 (should take an integer exit status parameter)
27 @param output_func: A function to call with each line of output from
28 the subprocess (should take a string parameter)
29 @param output_prefix: A string to pre-pend to each line of the output,
30 before passing it to stdout_func
31 @param timeout: Time duration (in seconds) to wait for the subprocess to
32 terminate before returning
33
34 @return: A kvm_tail object.
35 """
36 process = kvm_tail(command=command,
37 termination_func=termination_func,
38 output_func=output_func,
39 output_prefix=output_prefix)
40
41 end_time = time.time() + timeout
42 while time.time() < end_time and process.is_alive():
43 time.sleep(0.1)
44
45 return process
46
47
48def run_fg(command, output_func=None, output_prefix="", timeout=1.0):
49 """
50 Run command as a subprocess. Call output_func with each line of output
51 from the subprocess (prefixed by prefix). Return when timeout expires or
52 when the subprocess exits -- whichever occurs first. If timeout expires
53 and the subprocess is still running, kill it before returning.
54
55 @brief: Run a subprocess in the foreground and collect its output and
56 exit status.
57
58 @param command: The shell command to execute
59 @param output_func: A function to call with each line of output from
60 the subprocess (should take a string parameter)
61 @param output_prefix: A string to pre-pend to each line of the output,
62 before passing it to stdout_func
63 @param timeout: Time duration (in seconds) to wait for the subprocess to
64 terminate before killing it and returning
65
66 @return: A 2-tuple containing the exit status of the process and its
67 STDOUT/STDERR output. If timeout expires before the process
68 terminates, the returned status is None.
69 """
70 process = run_bg(command, None, output_func, output_prefix, timeout)
71 output = process.get_output()
72 if process.is_alive():
73 status = None
74 else:
75 status = process.get_status()
76 process.close()
77 return (status, output)
78
79
80def _lock(filename):
81 if not os.path.exists(filename):
82 open(filename, "w").close()
83 fd = os.open(filename, os.O_RDWR)
84 fcntl.lockf(fd, fcntl.LOCK_EX)
85 return fd
86
87
88def _unlock(fd):
89 fcntl.lockf(fd, fcntl.LOCK_UN)
90 os.close(fd)
91
92
93def _locked(filename):
94 try:
95 fd = os.open(filename, os.O_RDWR)
96 except:
97 return False
98 try:
99 fcntl.lockf(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
100 except:
101 os.close(fd)
102 return True
103 fcntl.lockf(fd, fcntl.LOCK_UN)
104 os.close(fd)
105 return False
106
107
108def _wait(filename):
109 fd = _lock(filename)
110 _unlock(fd)
111
112
113def _get_filenames(base_dir, id):
114 return [os.path.join(base_dir, s + id) for s in
115 "shell-pid-", "status-", "output-", "inpipe-",
116 "lock-server-running-", "lock-client-starting-"]
117
118
119def _get_reader_filename(base_dir, id, reader):
120 return os.path.join(base_dir, "outpipe-%s-%s" % (reader, id))
121
122
123class kvm_spawn:
124 """
125 This class is used for spawning and controlling a child process.
126
127 A new instance of this class can either run a new server (a small Python
128 program that reads output from the child process and reports it to the
129 client and to a text file) or attach to an already running server.
130 When a server is started it runs the child process.
131 The server writes output from the child's STDOUT and STDERR to a text file.
132 The text file can be accessed at any time using get_output().
133 In addition, the server opens as many pipes as requested by the client and
134 writes the output to them.
135 The pipes are requested and accessed by classes derived from kvm_spawn.
136 These pipes are referred to as "readers".
137 The server also receives input from the client and sends it to the child
138 process.
139 An instance of this class can be pickled. Every derived class is
140 responsible for restoring its own state by properly defining
141 __getinitargs__().
142
143 The first named pipe is used by _tail(), a function that runs in the
144 background and reports new output from the child as it is produced.
145 The second named pipe is used by a set of functions that read and parse
146 output as requested by the user in an interactive manner, similar to
147 pexpect.
148 When unpickled it automatically
149 resumes _tail() if needed.
150 """
151
152 def __init__(self, command=None, id=None, echo=False, linesep="\n"):
153 """
154 Initialize the class and run command as a child process.
155
156 @param command: Command to run, or None if accessing an already running
157 server.
158 @param id: ID of an already running server, if accessing a running
159 server, or None if starting a new one.
160 @param echo: Boolean indicating whether echo should be initially
161 enabled for the pseudo terminal running the subprocess. This
162 parameter has an effect only when starting a new server.
163 @param linesep: Line separator to be appended to strings sent to the
164 child process by sendline().
165 """
166 self.id = id or kvm_utils.generate_random_string(8)
167
168 # Define filenames for communication with server
169 base_dir = "/tmp/kvm_spawn"
170 try:
171 os.makedirs(base_dir)
172 except:
173 pass
174 (self.shell_pid_filename,
175 self.status_filename,
176 self.output_filename,
177 self.inpipe_filename,
178 self.lock_server_running_filename,
179 self.lock_client_starting_filename) = _get_filenames(base_dir,
180 self.id)
181
182 # Remember some attributes
183 self.echo = echo
184 self.linesep = linesep
185
186 # Make sure the 'readers' and 'close_hooks' attributes exist
187 if not hasattr(self, "readers"):
188 self.readers = []
189 if not hasattr(self, "close_hooks"):
190 self.close_hooks = []
191
192 # Define the reader filenames
193 self.reader_filenames = dict(
194 (reader, _get_reader_filename(base_dir, self.id, reader))
195 for reader in self.readers)
196
197 # Let the server know a client intends to open some pipes;
198 # if the executed command terminates quickly, the server will wait for
199 # the client to release the lock before exiting
200 lock_client_starting = _lock(self.lock_client_starting_filename)
201
202 # Start the server (which runs the command)
203 if command:
204 sub = subprocess.Popen("python %s" % __file__,
205 shell=True,
206 stdin=subprocess.PIPE,
207 stdout=subprocess.PIPE,
208 stderr=subprocess.STDOUT)
209 # Send parameters to the server
210 sub.stdin.write("%s\n" % self.id)
211 sub.stdin.write("%s\n" % echo)
212 sub.stdin.write("%s\n" % ",".join(self.readers))
213 sub.stdin.write("%s\n" % command)
214 # Wait for the server to complete its initialization
lmrb8f53d62009-07-27 13:29:17 +0000215 while not "Server %s ready" % self.id in sub.stdout.readline():
216 pass
lmre7a732b2009-08-07 20:13:37 +0000217 # Remember the start time for is_alive()
218 self.start_time = time.time()
lmrbbc9dd52009-07-22 20:33:47 +0000219
220 # Open the reading pipes
221 self.reader_fds = {}
222 try:
223 assert(_locked(self.lock_server_running_filename))
224 for reader, filename in self.reader_filenames.items():
225 self.reader_fds[reader] = os.open(filename, os.O_RDONLY)
226 except:
227 pass
228
229 # Allow the server to continue
230 _unlock(lock_client_starting)
231
232
233 # The following two functions are defined to make sure the state is set
234 # exclusively by the constructor call as specified in __getinitargs__().
235
236 def __getstate__(self):
237 pass
238
239
240 def __setstate__(self, state):
241 pass
242
243
244 def __getinitargs__(self):
245 # Save some information when pickling -- will be passed to the
246 # constructor upon unpickling
247 return (None, self.id, self.echo, self.linesep)
248
249
250 def _add_reader(self, reader):
251 """
252 Add a reader whose file descriptor can be obtained with _get_fd().
253 Should be called before __init__(). Intended for use by derived
254 classes.
255
256 @param reader: The name of the reader.
257 """
258 if not hasattr(self, "readers"):
259 self.readers = []
260 self.readers.append(reader)
261
262
263 def _add_close_hook(self, hook):
264 """
265 Add a close hook function to be called when close() is called.
266 The function will be called after the process terminates but before
267 final cleanup. Intended for use by derived classes.
268
269 @param hook: The hook function.
270 """
271 if not hasattr(self, "close_hooks"):
272 self.close_hooks = []
273 self.close_hooks.append(hook)
274
275
276 def _get_fd(self, reader):
277 """
278 Return an open file descriptor corresponding to the specified reader
279 pipe. If no such reader exists, or the pipe could not be opened,
280 return None. Intended for use by derived classes.
281
282 @param reader: The name of the reader.
283 """
284 return self.reader_fds.get(reader)
285
286
287 def get_id(self):
288 """
289 Return the instance's id attribute, which may be used to access the
290 process in the future.
291 """
292 return self.id
293
294
295 def get_shell_pid(self):
296 """
297 Return the PID of the subshell process, or None if not available.
298 The subshell is the shell that runs the command.
299 """
300 try:
301 file = open(self.shell_pid_filename, "r")
302 pid = int(file.read())
303 file.close()
304 return pid
305 except:
306 return None
307
308
309 def get_pid(self, index=0):
310 """
311 Try to get and return the PID of a child process of the subshell.
312 This is usually the PID of the process executed in the subshell.
313 There are 3 exceptions:
314 - If the subshell couldn't start the process for some reason, no
315 PID can be returned.
316 - If the subshell is running several processes in parallel,
317 multiple PIDs can be returned. Use the index parameter in this
318 case.
319 - Before starting the process, after the process has terminated,
320 or while running shell code that doesn't start any processes --
321 no PID can be returned.
322
323 @param index: The index of the child process whose PID is requested.
324 Normally this should remain 0.
325 @return: The PID of the child process, or None if none could be found.
326 """
327 parent_pid = self.get_shell_pid()
328 if not parent_pid:
329 return None
330 pids = commands.getoutput("ps --ppid %d -o pid=" % parent_pid).split()
331 try:
332 return int(pids[index])
333 except:
334 return None
335
336
337 def get_status(self):
338 """
339 Wait for the process to exit and return its exit status, or None
340 if the exit status is not available.
341 """
342 _wait(self.lock_server_running_filename)
343 try:
344 file = open(self.status_filename, "r")
345 status = int(file.read())
346 file.close()
347 return status
348 except:
349 return None
350
351
352 def get_output(self):
353 """
354 Return the STDOUT and STDERR output of the process so far.
355 """
356 try:
357 file = open(self.output_filename, "r")
358 output = file.read()
359 file.close()
360 return output
361 except:
362 return ""
363
364
365 def is_alive(self):
366 """
367 Return True if the process is running.
368 """
369 pid = self.get_shell_pid()
370 # See if the PID exists
371 try:
372 os.kill(pid, 0)
373 except:
374 return False
375 # Make sure the PID belongs to the original process
376 filename = "/proc/%d/cmdline" % pid
377 try:
378 file = open(filename, "r")
379 cmdline = file.read()
380 file.close()
381 except:
382 # If we couldn't find the file for some reason, skip the check
383 return True
lmre7a732b2009-08-07 20:13:37 +0000384 # If this process is new (less than 10 secs old) skip the check
385 if hasattr(self, "start_time") and time.time() < self.start_time + 10:
386 return True
387 # Perform the check
lmrbbc9dd52009-07-22 20:33:47 +0000388 if self.id in cmdline:
389 return True
390 return False
391
392
393 def close(self, sig=signal.SIGTERM):
394 """
395 Kill the child process if it's alive and remove temporary files.
396
397 @param sig: The signal to send the process when attempting to kill it.
398 """
399 # Kill it if it's alive
400 if self.is_alive():
401 try:
402 os.kill(self.get_shell_pid(), sig)
403 except:
404 pass
405 # Wait for the server to exit
406 _wait(self.lock_server_running_filename)
407 # Call all cleanup routines
408 for hook in self.close_hooks:
409 hook()
410 # Close reader file descriptors
411 for fd in self.reader_fds.values():
412 try:
413 os.close(fd)
414 except:
415 pass
416 # Remove all used files
417 for filename in (_get_filenames("/tmp/kvm_spawn", self.id) +
418 self.reader_filenames.values()):
419 try:
420 os.unlink(filename)
421 except OSError:
422 pass
423
424
425 def set_linesep(self, linesep):
426 """
427 Sets the line separator string (usually "\\n").
428
429 @param linesep: Line separator string.
430 """
431 self.linesep = linesep
432
433
434 def send(self, str=""):
435 """
436 Send a string to the child process.
437
438 @param str: String to send to the child process.
439 """
440 try:
441 fd = os.open(self.inpipe_filename, os.O_RDWR)
442 os.write(fd, str)
443 os.close(fd)
444 except:
445 pass
446
447
448 def sendline(self, str=""):
449 """
450 Send a string followed by a line separator to the child process.
451
452 @param str: String to send to the child process.
453 """
454 self.send(str + self.linesep)
455
456
457class kvm_tail(kvm_spawn):
458 """
459 This class runs a child process in the background and sends its output in
460 real time, line-by-line, to a callback function.
461
462 See kvm_spawn's docstring.
463
464 This class uses a single pipe reader to read data in real time from the
465 child process and report it to a given callback function.
466 When the child process exits, its exit status is reported to an additional
467 callback function.
468
469 When this class is unpickled, it automatically resumes reporting output.
470 """
471
472 def __init__(self, command=None, id=None, echo=False, linesep="\n",
473 termination_func=None, output_func=None, output_prefix=""):
474 """
475 Initialize the class and run command as a child process.
476
477 @param command: Command to run, or None if accessing an already running
478 server.
479 @param id: ID of an already running server, if accessing a running
480 server, or None if starting a new one.
481 @param echo: Boolean indicating whether echo should be initially
482 enabled for the pseudo terminal running the subprocess. This
483 parameter has an effect only when starting a new server.
484 @param linesep: Line separator to be appended to strings sent to the
485 child process by sendline().
486 @param termination_func: Function to call when the process exits. The
487 function must accept a single exit status parameter.
488 @param output_func: Function to call whenever a line of output is
489 available from the STDOUT or STDERR streams of the process.
490 The function must accept a single string parameter. The string
491 does not include the final newline.
492 @param output_prefix: String to prepend to lines sent to output_func.
493 """
494 # Add a reader and a close hook
495 self._add_reader("tail")
496 self._add_close_hook(self._join_thread)
497
498 # Init the superclass
499 kvm_spawn.__init__(self, command, id, echo, linesep)
500
501 # Remember some attributes
502 self.termination_func = termination_func
503 self.output_func = output_func
504 self.output_prefix = output_prefix
505
506 # Start the thread in the background
507 self.tail_thread = threading.Thread(None, self._tail)
508 self.tail_thread.start()
509
510
511 def __getinitargs__(self):
512 return kvm_spawn.__getinitargs__(self) + (self.termination_func,
513 self.output_func,
514 self.output_prefix)
515
516
517 def set_termination_func(self, termination_func):
518 """
519 Set the termination_func attribute. See __init__() for details.
520
521 @param termination_func: Function to call when the process terminates.
522 Must take a single parameter -- the exit status.
523 """
524 self.termination_func = termination_func
525
526
527 def set_output_func(self, output_func):
528 """
529 Set the output_func attribute. See __init__() for details.
530
531 @param output_func: Function to call for each line of STDOUT/STDERR
532 output from the process. Must take a single string parameter.
533 """
534 self.output_func = output_func
535
536
537 def set_output_prefix(self, output_prefix):
538 """
539 Set the output_prefix attribute. See __init__() for details.
540
541 @param output_prefix: String to pre-pend to each line sent to
542 output_func (see set_output_callback()).
543 """
544 self.output_prefix = output_prefix
545
546
547 def _tail(self):
548 def print_line(text):
549 # Pre-pend prefix and remove trailing whitespace
550 text = self.output_prefix + text.rstrip()
551 # Sanitize text
552 text = text.decode("utf-8", "replace")
553 # Pass it to output_func
554 try:
555 self.output_func(text)
556 except TypeError:
557 pass
558
559 fd = self._get_fd("tail")
560 buffer = ""
561 while True:
562 try:
563 # See if there's any data to read from the pipe
564 r, w, x = select.select([fd], [], [], 0.05)
565 except:
566 break
567 if fd in r:
568 # Some data is available; read it
569 new_data = os.read(fd, 1024)
570 if not new_data:
571 break
572 buffer += new_data
573 # Send the output to output_func line by line
574 # (except for the last line)
575 if self.output_func:
576 lines = buffer.split("\n")
577 for line in lines[:-1]:
578 print_line(line)
579 # Leave only the last line
580 last_newline_index = buffer.rfind("\n")
581 buffer = buffer[last_newline_index+1:]
582 else:
583 # No output is available right now; flush the buffer
584 if buffer:
585 print_line(buffer)
586 buffer = ""
587 # The process terminated; print any remaining output
588 if buffer:
589 print_line(buffer)
590 # Get the exit status, print it and send it to termination_func
591 status = self.get_status()
592 if status is None:
593 return
594 print_line("(Process terminated with status %s)" % status)
595 try:
596 self.termination_func(status)
597 except TypeError:
598 pass
599
600
601 def _join_thread(self):
602 # Wait for the tail thread to exit
603 if self.tail_thread:
604 self.tail_thread.join()
605
606
607class kvm_expect(kvm_tail):
608 """
609 This class runs a child process in the background and provides expect-like
610 services.
611
612 It also provides all of kvm_tail's functionality.
613 """
614
615 def __init__(self, command=None, id=None, echo=False, linesep="\n",
616 termination_func=None, output_func=None, output_prefix=""):
617 """
618 Initialize the class and run command as a child process.
619
620 @param command: Command to run, or None if accessing an already running
621 server.
622 @param id: ID of an already running server, if accessing a running
623 server, or None if starting a new one.
624 @param echo: Boolean indicating whether echo should be initially
625 enabled for the pseudo terminal running the subprocess. This
626 parameter has an effect only when starting a new server.
627 @param linesep: Line separator to be appended to strings sent to the
628 child process by sendline().
629 @param termination_func: Function to call when the process exits. The
630 function must accept a single exit status parameter.
631 @param output_func: Function to call whenever a line of output is
632 available from the STDOUT or STDERR streams of the process.
633 The function must accept a single string parameter. The string
634 does not include the final newline.
635 @param output_prefix: String to prepend to lines sent to output_func.
636 """
637 # Add a reader
638 self._add_reader("expect")
639
640 # Init the superclass
641 kvm_tail.__init__(self, command, id, echo, linesep,
642 termination_func, output_func, output_prefix)
643
644
645 def __getinitargs__(self):
646 return kvm_tail.__getinitargs__(self)
647
648
649 def read_nonblocking(self, timeout=None):
650 """
651 Read from child until there is nothing to read for timeout seconds.
652
653 @param timeout: Time (seconds) to wait before we give up reading from
654 the child process, or None to use the default value.
655 """
656 if timeout is None:
657 timeout = 0.1
658 fd = self._get_fd("expect")
659 data = ""
660 while True:
661 try:
662 r, w, x = select.select([fd], [], [], timeout)
663 except:
664 return data
665 if fd in r:
666 new_data = os.read(fd, 1024)
667 if not new_data:
668 return data
669 data += new_data
670 else:
671 return data
672
673
674 def match_patterns(self, str, patterns):
675 """
676 Match str against a list of patterns.
677
678 Return the index of the first pattern that matches a substring of str.
679 None and empty strings in patterns are ignored.
680 If no match is found, return None.
681
682 @param patterns: List of strings (regular expression patterns).
683 """
684 for i in range(len(patterns)):
685 if not patterns[i]:
686 continue
687 if re.search(patterns[i], str):
688 return i
689
690
691 def read_until_output_matches(self, patterns, filter=lambda x: x,
692 timeout=30.0, internal_timeout=None,
693 print_func=None):
694 """
695 Read using read_nonblocking until a match is found using match_patterns,
696 or until timeout expires. Before attempting to search for a match, the
697 data is filtered using the filter function provided.
698
699 @brief: Read from child using read_nonblocking until a pattern
700 matches.
701 @param patterns: List of strings (regular expression patterns)
702 @param filter: Function to apply to the data read from the child before
703 attempting to match it against the patterns (should take and
704 return a string)
705 @param timeout: The duration (in seconds) to wait until a match is
706 found
707 @param internal_timeout: The timeout to pass to read_nonblocking
708 @param print_func: A function to be used to print the data being read
709 (should take a string parameter)
710 @return: Tuple containing the match index (or None if no match was
711 found) and the data read so far.
712 """
713 match = None
714 data = ""
715
716 end_time = time.time() + timeout
717 while time.time() < end_time:
718 # Read data from child
719 newdata = self.read_nonblocking(internal_timeout)
720 # Print it if necessary
721 if print_func and newdata:
722 str = newdata
723 if str.endswith("\n"):
724 str = str[:-1]
725 for line in str.split("\n"):
726 print_func(line.decode("utf-8", "replace"))
727 data += newdata
728
729 done = False
730 # Look for patterns
731 match = self.match_patterns(filter(data), patterns)
732 if match is not None:
733 done = True
734 # Check if child has died
735 if not self.is_alive():
736 logging.debug("Process terminated with status %s" % self.get_status())
737 done = True
738 # Are we done?
739 if done: break
740
741 # Print some debugging info
742 if match is None and (self.is_alive() or self.get_status() != 0):
743 logging.debug("Timeout elapsed or process terminated. Output:" +
744 kvm_utils.format_str_for_message(data.strip()))
745
746 return (match, data)
747
748
749 def read_until_last_word_matches(self, patterns, timeout=30.0,
750 internal_timeout=None, print_func=None):
751 """
752 Read using read_nonblocking until the last word of the output matches
753 one of the patterns (using match_patterns), or until timeout expires.
754
755 @param patterns: A list of strings (regular expression patterns)
756 @param timeout: The duration (in seconds) to wait until a match is
757 found
758 @param internal_timeout: The timeout to pass to read_nonblocking
759 @param print_func: A function to be used to print the data being read
760 (should take a string parameter)
761 @return: A tuple containing the match index (or None if no match was
762 found) and the data read so far.
763 """
764 def get_last_word(str):
765 if str:
766 return str.split()[-1]
767 else:
768 return ""
769
770 return self.read_until_output_matches(patterns, get_last_word,
771 timeout, internal_timeout,
772 print_func)
773
774
775 def read_until_last_line_matches(self, patterns, timeout=30.0,
776 internal_timeout=None, print_func=None):
777 """
778 Read using read_nonblocking until the last non-empty line of the output
779 matches one of the patterns (using match_patterns), or until timeout
780 expires. Return a tuple containing the match index (or None if no match
781 was found) and the data read so far.
782
783 @brief: Read using read_nonblocking until the last non-empty line
784 matches a pattern.
785
786 @param patterns: A list of strings (regular expression patterns)
787 @param timeout: The duration (in seconds) to wait until a match is
788 found
789 @param internal_timeout: The timeout to pass to read_nonblocking
790 @param print_func: A function to be used to print the data being read
791 (should take a string parameter)
792 """
793 def get_last_nonempty_line(str):
794 nonempty_lines = [l for l in str.splitlines() if l.strip()]
795 if nonempty_lines:
796 return nonempty_lines[-1]
797 else:
798 return ""
799
800 return self.read_until_output_matches(patterns, get_last_nonempty_line,
801 timeout, internal_timeout,
802 print_func)
803
804
805class kvm_shell_session(kvm_expect):
806 """
807 This class runs a child process in the background. It it suited for
808 processes that provide an interactive shell, such as SSH and Telnet.
809
810 It provides all services of kvm_expect and kvm_tail. In addition, it
811 provides command running services, and a utility function to test the
812 process for responsiveness.
813 """
814
815 def __init__(self, command=None, id=None, echo=False, linesep="\n",
816 termination_func=None, output_func=None, output_prefix="",
817 prompt=r"[\#\$]\s*$", status_test_command="echo $?"):
818 """
819 Initialize the class and run command as a child process.
820
821 @param command: Command to run, or None if accessing an already running
822 server.
823 @param id: ID of an already running server, if accessing a running
824 server, or None if starting a new one.
825 @param echo: Boolean indicating whether echo should be initially
826 enabled for the pseudo terminal running the subprocess. This
827 parameter has an effect only when starting a new server.
828 @param linesep: Line separator to be appended to strings sent to the
829 child process by sendline().
830 @param termination_func: Function to call when the process exits. The
831 function must accept a single exit status parameter.
832 @param output_func: Function to call whenever a line of output is
833 available from the STDOUT or STDERR streams of the process.
834 The function must accept a single string parameter. The string
835 does not include the final newline.
836 @param output_prefix: String to prepend to lines sent to output_func.
837 @param prompt: Regular expression describing the shell's prompt line.
838 @param status_test_command: Command to be used for getting the last
839 exit status of commands run inside the shell (used by
840 get_command_status_output() and friends).
841 """
842 # Init the superclass
843 kvm_expect.__init__(self, command, id, echo, linesep,
844 termination_func, output_func, output_prefix)
845
846 # Remember some attributes
847 self.prompt = prompt
848 self.status_test_command = status_test_command
849
850
851 def __getinitargs__(self):
852 return kvm_expect.__getinitargs__(self) + (self.prompt,
853 self.status_test_command)
854
855
856 def set_prompt(self, prompt):
857 """
858 Set the prompt attribute for later use by read_up_to_prompt.
859
860 @param: String that describes the prompt contents.
861 """
862 self.prompt = prompt
863
864
865 def set_status_test_command(self, status_test_command):
866 """
867 Set the command to be sent in order to get the last exit status.
868
869 @param status_test_command: Command that will be sent to get the last
870 exit status.
871 """
872 self.status_test_command = status_test_command
873
874
875 def is_responsive(self, timeout=5.0):
876 """
877 Return True if the process responds to STDIN/terminal input.
878
879 Send a newline to the child process (e.g. SSH or Telnet) and read some
880 output using read_nonblocking().
881 If all is OK, some output should be available (e.g. the shell prompt).
882 In that case return True. Otherwise return False.
883
884 @param timeout: Time duration to wait before the process is considered
885 unresponsive.
886 """
887 # Read all output that's waiting to be read, to make sure the output
888 # we read next is in response to the newline sent
889 self.read_nonblocking(timeout=0.1)
890 # Send a newline
891 self.sendline()
892 # Wait up to timeout seconds for some output from the child
893 end_time = time.time() + timeout
894 while time.time() < end_time:
895 time.sleep(0.5)
896 if self.read_nonblocking(timeout=0).strip():
897 return True
898 # No output -- report unresponsive
899 return False
900
901
902 def read_up_to_prompt(self, timeout=30.0, internal_timeout=None,
903 print_func=None):
904 """
905 Read using read_nonblocking until the last non-empty line of the output
906 matches the prompt regular expression set by set_prompt, or until
907 timeout expires.
908
909 @brief: Read using read_nonblocking until the last non-empty line
910 matches the prompt.
911
912 @param timeout: The duration (in seconds) to wait until a match is
913 found
914 @param internal_timeout: The timeout to pass to read_nonblocking
915 @param print_func: A function to be used to print the data being
916 read (should take a string parameter)
917
918 @return: A tuple containing True/False indicating whether the prompt
919 was found, and the data read so far.
920 """
921 (match, output) = self.read_until_last_line_matches([self.prompt],
922 timeout,
923 internal_timeout,
924 print_func)
925 return (match is not None, output)
926
927
928 def get_command_status_output(self, command, timeout=30.0,
929 internal_timeout=None, print_func=None):
930 """
931 Send a command and return its exit status and output.
932
933 @param command: Command to send (must not contain newline characters)
934 @param timeout: The duration (in seconds) to wait until a match is
935 found
936 @param internal_timeout: The timeout to pass to read_nonblocking
937 @param print_func: A function to be used to print the data being read
938 (should take a string parameter)
939
940 @return: A tuple (status, output) where status is the exit status or
941 None if no exit status is available (e.g. timeout elapsed), and
942 output is the output of command.
943 """
944 def remove_command_echo(str, cmd):
945 if str and str.splitlines()[0] == cmd:
946 str = "".join(str.splitlines(True)[1:])
947 return str
948
949 def remove_last_nonempty_line(str):
950 return "".join(str.rstrip().splitlines(True)[:-1])
951
952 # Print some debugging info
953 logging.debug("Sending command: %s" % command)
954
955 # Read everything that's waiting to be read
956 self.read_nonblocking(0.1)
957
958 # Send the command and get its output
959 self.sendline(command)
960 (match, output) = self.read_up_to_prompt(timeout, internal_timeout,
961 print_func)
962 # Remove the echoed command from the output
963 output = remove_command_echo(output, command)
964 # If the prompt was not found, return the output so far
965 if not match:
966 return (None, output)
967 # Remove the final shell prompt from the output
968 output = remove_last_nonempty_line(output)
969
970 # Send the 'echo ...' command to get the last exit status
971 self.sendline(self.status_test_command)
972 (match, status) = self.read_up_to_prompt(10.0, internal_timeout)
973 if not match:
974 return (None, output)
975 status = remove_command_echo(status, self.status_test_command)
976 status = remove_last_nonempty_line(status)
977 # Get the first line consisting of digits only
978 digit_lines = [l for l in status.splitlines() if l.strip().isdigit()]
979 if not digit_lines:
980 return (None, output)
981 status = int(digit_lines[0].strip())
982
983 # Print some debugging info
984 if status != 0:
985 logging.debug("Command failed; status: %d, output:%s", status,
986 kvm_utils.format_str_for_message(output.strip()))
987
988 return (status, output)
989
990
991 def get_command_status(self, command, timeout=30.0, internal_timeout=None,
992 print_func=None):
993 """
994 Send a command and return its exit status.
995
996 @param command: Command to send
997 @param timeout: The duration (in seconds) to wait until a match is
998 found
999 @param internal_timeout: The timeout to pass to read_nonblocking
1000 @param print_func: A function to be used to print the data being read
1001 (should take a string parameter)
1002
1003 @return: Exit status or None if no exit status is available (e.g.
1004 timeout elapsed).
1005 """
1006 (status, output) = self.get_command_status_output(command, timeout,
1007 internal_timeout,
1008 print_func)
1009 return status
1010
1011
1012 def get_command_output(self, command, timeout=30.0, internal_timeout=None,
1013 print_func=None):
1014 """
1015 Send a command and return its output.
1016
1017 @param command: Command to send
1018 @param timeout: The duration (in seconds) to wait until a match is
1019 found
1020 @param internal_timeout: The timeout to pass to read_nonblocking
1021 @param print_func: A function to be used to print the data being read
1022 (should take a string parameter)
1023 """
1024 (status, output) = self.get_command_status_output(command, timeout,
1025 internal_timeout,
1026 print_func)
1027 return output
1028
1029
1030# The following is the server part of the module.
1031
1032def _server_main():
1033 id = sys.stdin.readline().strip()
1034 echo = sys.stdin.readline().strip() == "True"
1035 readers = sys.stdin.readline().strip().split(",")
1036 command = sys.stdin.readline().strip() + " && echo %s > /dev/null" % id
1037
1038 # Define filenames to be used for communication
1039 base_dir = "/tmp/kvm_spawn"
1040 (shell_pid_filename,
1041 status_filename,
1042 output_filename,
1043 inpipe_filename,
1044 lock_server_running_filename,
1045 lock_client_starting_filename) = _get_filenames(base_dir, id)
1046
1047 # Populate the reader filenames list
1048 reader_filenames = [_get_reader_filename(base_dir, id, reader)
1049 for reader in readers]
1050
1051 # Set $TERM = dumb
1052 os.putenv("TERM", "dumb")
1053
1054 (shell_pid, shell_fd) = pty.fork()
1055 if shell_pid == 0:
1056 # Child process: run the command in a subshell
1057 os.execv("/bin/sh", ["/bin/sh", "-c", command])
1058 else:
1059 # Parent process
1060 lock_server_running = _lock(lock_server_running_filename)
1061
1062 # Set terminal echo on/off and disable pre- and post-processing
1063 attr = termios.tcgetattr(shell_fd)
1064 attr[0] &= ~termios.INLCR
1065 attr[0] &= ~termios.ICRNL
1066 attr[0] &= ~termios.IGNCR
1067 attr[1] &= ~termios.OPOST
1068 if echo:
1069 attr[3] |= termios.ECHO
1070 else:
1071 attr[3] &= ~termios.ECHO
1072 termios.tcsetattr(shell_fd, termios.TCSANOW, attr)
1073
1074 # Open output file
1075 output_file = open(output_filename, "w")
1076 # Open input pipe
1077 os.mkfifo(inpipe_filename)
1078 inpipe_fd = os.open(inpipe_filename, os.O_RDWR)
1079 # Open output pipes (readers)
1080 reader_fds = []
1081 for filename in reader_filenames:
1082 os.mkfifo(filename)
1083 reader_fds.append(os.open(filename, os.O_RDWR))
1084
1085 # Write shell PID to file
1086 file = open(shell_pid_filename, "w")
1087 file.write(str(shell_pid))
1088 file.close()
1089
1090 # Print something to stdout so the client can start working
lmrb8f53d62009-07-27 13:29:17 +00001091 print "Server %s ready" % id
lmrbbc9dd52009-07-22 20:33:47 +00001092 sys.stdout.flush()
1093
1094 # Initialize buffers
1095 buffers = ["" for reader in readers]
1096
1097 # Read from child and write to files/pipes
1098 while True:
1099 # Make a list of reader pipes whose buffers are not empty
1100 fds = [fd for (i, fd) in enumerate(reader_fds) if buffers[i]]
1101 # Wait until there's something to do
1102 r, w, x = select.select([shell_fd, inpipe_fd], fds, [])
1103 # If a reader pipe is ready for writing --
1104 for (i, fd) in enumerate(reader_fds):
1105 if fd in w:
1106 bytes_written = os.write(fd, buffers[i])
1107 buffers[i] = buffers[i][bytes_written:]
1108 # If there's data to read from the child process --
1109 if shell_fd in r:
1110 try:
1111 data = os.read(shell_fd, 16384)
1112 except OSError:
1113 break
1114 # Remove carriage returns from the data -- they often cause
1115 # trouble and are normally not needed
1116 data = data.replace("\r", "")
1117 output_file.write(data)
1118 output_file.flush()
1119 for i in range(len(readers)):
1120 buffers[i] += data
1121 # If there's data to read from the client --
1122 if inpipe_fd in r:
1123 data = os.read(inpipe_fd, 1024)
1124 os.write(shell_fd, data)
1125
1126 # Wait for the shell process to exit and get its exit status
1127 status = os.waitpid(shell_pid, 0)[1]
1128 status = os.WEXITSTATUS(status)
1129 file = open(status_filename, "w")
1130 file.write(str(status))
1131 file.close()
1132
1133 # Wait for the client to finish initializing
1134 _wait(lock_client_starting_filename)
1135
1136 # Delete FIFOs
1137 for filename in reader_filenames + [inpipe_filename]:
1138 try:
1139 os.unlink(filename)
1140 except OSError:
1141 pass
1142
1143 # Close all files and pipes
1144 output_file.close()
1145 os.close(inpipe_fd)
1146 for fd in reader_fds:
1147 os.close(fd)
1148
1149 _unlock(lock_server_running)
1150
1151
1152if __name__ == "__main__":
1153 _server_main()