import repository from arizona
[raven.git] / lib / arizona-lib / arizonageneral.py
1 #! /usr/bin/env python
2
3 """
4 <Program Name>
5    arizonageneral.py
6
7 <Started>
8    April 27, 2004
9
10 <Author>
11    Justin Cappos, with new routines added by members of the Stork team.
12
13 <Purpose>
14    General python routines.
15 """
16
17 import atexit
18 import os
19 import re
20 import signal
21 import smtplib
22 import sys
23 import traceback
24 import time
25 import socket
26 import select
27 import inspect
28 import types
29 import random
30
31 # Justin: This is needed because syslog does not exist on Windows.   We don't 
32 # use any function that does a syslog on Windows so this should be okay.
33 try:
34   import syslog
35 except ImportError:
36   pass
37
38
39 def uniq(orig_list):
40    """ 
41    <Purpose>
42       Performs "uniq" on a list (returns the unique items in a list), with
43       the new list retaining the order of the original list.  Doesn't use 
44       sort.
45
46    <Arguments>
47       orig_list:
48               List or other iterable type from which to collect unique 
49               items.  
50
51    <Exceptions>
52       TypeError if a bad parameter is detected.
53    
54    <Side Effects>
55       None.
56
57    <Returns>
58       Returns a list of the unique items in the original list.
59    """
60    # check params
61    check_type(orig_list, "orig_list", [None, "iterable"], "uniq")
62    
63    # None returns an empty list
64    uniq_list = []
65    if not orig_list:
66       return uniq_list
67       
68    # check each item in the original list   
69    for item in orig_list:
70    
71       # if this item wasn't in our new uniq list, add it
72       if item not in uniq_list:
73          uniq_list = uniq_list + [item]
74          
75    return uniq_list
76       
77       
78       
79       
80       
81 def intersect(list_a, list_b):
82    """ 
83    <Purpose>
84       Performs "intersect" on two lists (returns the unique items in a list), 
85       with the new list retaining the order of list_a.  ###If duplicate items 
86       appear in list_a, the result may contain duplicate items.  ### 
87
88    <Arguments>
89       list_a:
90               List or other iterable type from which to collect unique 
91               items.  
92       list_b:
93               List or other iterable type from which to collect unique 
94               items.  
95
96    <Exceptions>
97       TypeError if a bad parameter is detected.
98    
99    <Side Effects>
100       None.
101
102    <Returns>
103       Returns a list containing the intersection of lists a and b
104    """
105    # check params
106    check_type(list_a, "list_a", [None, "iterable"], "intersect")
107    check_type(list_b, "list_b", [None, "iterable"], "intersect")
108    
109    # None returns an empty list
110    intersect_list = []
111    if not list_a or not list_b:
112       return intersect_list
113       
114    # check each item in list_a
115    for item in list_a:
116    
117       # if this item is in list_b, add it
118       if item in list_b:
119          intersect_list = intersect_list + [item]
120          
121    return intersect_list
122
123
124
125
126
127 def subtract(list_a, list_b):
128    """ 
129    <Purpose>
130       Performs "subtract" on two lists (returns the items in list_a that are
131       not in list_b)
132
133    <Arguments>
134       list_a:
135       list_b:
136
137    <Exceptions>
138       TypeError if a bad parameter is detected.
139    
140    <Side Effects>
141       None.
142
143    <Returns>
144       Returns a list containing the subtraction of lists a and b
145    """
146    # check params
147    check_type(list_a, "list_a", [None, "iterable"], "subtract")
148    check_type(list_b, "list_b", [None, "iterable"], "subtract")
149    
150    # None returns an empty list
151    subtract_list = []
152    if not list_a or not list_b:
153       return subtract_list
154       
155    # check each item in list_a
156    for item in list_a:
157       if not item in list_b:
158          subtract_list.append(item)
159          
160    return subtract_list
161
162       
163       
164
165       
166 def recur_depth(func_name):
167    """
168    <Purpose>
169       Returns the number of levels deep in recursion a function is.
170
171    <Arguments>
172       func_name:
173               Name of the function to find recursive depth for.
174
175    <Exceptions>
176       TypeError if a bad parameter is detected.
177    
178    <Side Effects>
179       None.
180
181    <Returns>
182       Returns the number of levels deep in recursion a function is.
183    """
184    # check params
185    check_type_simple(func_name, "func_name", str, "recur_depth")
186    
187    the_list = traceback.extract_stack()
188    count = 0
189
190    for the_item in the_list:
191       if the_item[2] == func_name:
192          count += 1
193
194    return count
195
196
197
198
199
200 def format_list(string_list, separator, head, headnew, tail, width=80):
201    """
202    <Purpose>
203       Builds a string representation of a list of strings, adding newline
204       characters to break up long lines, as appropriate.
205
206    <Arguments>
207       string_list: 
208               The list of strings to combine into a string.
209       separator: 
210               A string to separate the items.  Any trailing spaces will be
211               automatically removed to fit at the end of a line. 
212       head: 
213               Text to insert before the list.
214       headnew: 
215               Text to insert before a wrapped line (usually spaces).
216       tail: 
217               Text to insert after the list.
218       width: (default is 80)
219               Number of character columns to use.  Wrapping will occur as
220               lines grow longer than this.          
221
222    <Exceptions>
223       TypeError if a type mismatch or parameter error is detected.
224    
225    <Side Effects>
226       None.
227
228    <Returns>
229       String holding the formatted list contents.
230    """
231    # check params
232    check_type_stringlist(string_list, "string_list", "format_list")
233    check_type_simple(separator, "separator", str, "format_list", noneok=True)
234    check_type_simple(head, "head", str, "format_list", noneok=True)
235    check_type_simple(headnew, "headnew", str, "format_list", noneok=True)
236    check_type_simple(tail, "tail", str, "format_list", noneok=True)
237    
238    # set default values when None is passed in
239    if separator == None:
240       separator = ""
241    if head == None:
242       head = ""
243    if headnew == None:
244       headnew = ""   
245    if tail == None:
246       tail = ""
247    
248    items = len(string_list) - 1
249    maxspace = len(separator)
250    separator = separator.rstrip()
251    minsep = len(separator)
252    maxspace -= minsep
253    last = 0   
254
255    # add each item in the list
256    string = ""
257    line = head  # current line of text we're building
258    index = 0
259    for item in string_list:
260       
261       # is the line too long (does it need to be wrapped)?
262       if index >= items:
263          minsep = 0
264          
265       if len(line) + len(item) + minsep > width:
266          if string != "":
267            string += "\n"
268          string += line
269          last = len(line)
270          line = ""
271       
272       # If new output line.. add any new line header the user gave.   
273       # In either case, add a list item to the line.
274       if line == "":
275          line = headnew + item
276       else:
277          line += item
278          
279       # as long as we're not at the end of the list add user's separator
280       if index < items:
281          line = line + separator
282          spaces = width - len(line)
283          if maxspace < spaces:
284             spaces = maxspace
285          line += " " * spaces
286       
287       # keep track of what index we're at in the list   
288       index += 1         
289       
290    # deal with the last line 
291    if line != "":
292       if string != "":
293          string += "\n"
294       string += line
295       last = len(line)
296    
297    # add user's tail text
298    if last + len(tail) > width and string != "":
299       string += "\n"
300    string += tail
301    
302    return string
303
304
305
306
307
308 glo_child_failed = False
309
310 def __child_failed_handler(signum, frame):
311    """ Handles SIGUSR1 for system_timeout """
312    global glo_child_failed
313    glo_child_failed = True
314    return signum, frame
315
316
317
318
319
320
321 def system_timeout(command, tries, wait, kill_signal=15):
322    """ 
323    <Purpose>
324       Runs a command and terminates the command after a predefined time.
325       If the command fails to complete within "wait" seconds, it kills it
326       and tries again.   
327
328    <Arguments>
329       command:
330               Command to run.
331       tries:
332               Number of times to attempt to run the command.
333       wait:
334               Number of seconds to wait before killing the command. 
335       kill_signal:
336               Signal number to send to the process to kill it (Default SIGTERM)
337
338    <Exceptions>
339       TypeError if a bad parameter is detected.
340       
341       ValueError if either "tries" or "wait" are negative integers
342    
343    <Side Effects>
344       None.
345
346    <Returns>
347       Returns (bool, status)  where bool is True on normal exit, False on 
348       error or interruption by a signal.
349       
350       Status is the exit status of the command, 1 on error or timeout 
351       
352       If the command returns fail_id then this function will be confused 
353       into thinking that it failed to execute.      
354    """
355    # check params
356    check_type_simple(command, "command", str, "system_timeout")
357    check_type_simple(tries, "tries", int, "system_timeout")
358    check_type_simple(wait, "wait", int, "system_timeout")
359    check_type_simple(kill_signal, "kill_signal", int, "system_timeout")
360
361    # make sure that variables "tries" and "wait" are non-negative
362    if tries < 0:
363        raise ValueError, 'The second argument must be a non-negative integer'
364    if wait < 0:
365        raise ValueError, 'The third argument must be a non-negative integer'
366    
367    # set up signal handler for child failure
368    global glo_child_failed 
369    glo_child_failed = False
370    signal.signal(signal.SIGUSR1, __child_failed_handler)   
371
372    time_start = time.time()
373
374    # give more than one try 
375    our_tries = tries
376    while our_tries > 0:  
377       
378       time_this = time.time()
379       # fork the parent process into a duplicate child process
380       try:
381          child_pid = os.fork()
382       except OSError:
383          # Had a problem with fork so bomb out
384          syslog.syslog("Error : system_timeout() fork, "+str(sys.exc_info()[0])+" "+str(sys.exc_info()[1])+" "+str(traceback.format_tb(sys.exc_info()[2])))
385          raise
386          
387       # are we the child or the parent?
388       if child_pid == 0:
389          # child process: execute the command and exit (if I use execv, I have 
390          # to reparse the string as bash, etc. would)
391
392          # Justin:  we need to extract the exit status from the command if we
393          # let the command status pass through, we will lose the upper 2 bytes
394          # and will have the wrong status.
395          os._exit(os.WEXITSTATUS(os.system(command)))
396
397       else:
398          # parent
399          while time.time() - time_this < wait:
400             ( ret_pid, status ) = os.waitpid(child_pid, os.WNOHANG)
401             if ret_pid == child_pid:
402                
403                if glo_child_failed:
404                   return (False, 0)
405
406                # if exited cleanly...
407                if os.WIFEXITED(status):
408                   return (True, os.WEXITSTATUS(status))
409                elif os.WIFSIGNALED:
410                   return (True, os.WTERMSIG(status))
411             time.sleep(0)
412          else:
413             ########## TODO replace with Justin's routine arizonacrypt.sl_to_fn
414             # log netstat -altneep output for debugging
415             ( junk_in, the_out, the_err ) = os.popen3("netstat -altneep")
416             junk_in.close()
417             output = the_out.readline()
418             while output:
419                syslog.syslog("[netstat -altneep] " + output)
420                output = the_out.readline()
421
422             # kill the child
423             os.kill(child_pid, kill_signal)  
424             syslog.syslog("Error : system_timeout() Forced to kill command [" + str(tries) + " tries left] : " + command)
425
426             # wait until the child actually terminates  
427             os.waitpid(child_pid, 0)
428             our_tries -= 1
429
430    # out of tries.. log failure
431    time_elapsed = round(time.time() - time_start, 3) # round to 3 decimal places
432    syslog.syslog("Error : system_timeout() Forced to abort command [waited " + str(time_elapsed) + " seconds] :" + command)
433    
434    return (False, 0)
435
436
437
438
439 def system_timeout_backoff(command, tries, wait, kill_signal=15):
440    """ 
441    <Purpose>
442       Runs a command and terminates the command after a predefined time.
443       If the command fails to complete within "wait" seconds, it kills it
444       and tries again.
445       
446       [NOTE]
447       In this version of the function wait is multiplied by 2 after
448       each attempt
449
450    <Arguments>
451       command:
452               Command to run.
453       tries:
454               Number of times to attempt to run the command.
455       wait:
456               Number of seconds to wait before killing the command. 
457       kill_signal:
458               Signal number to send to the process to kill it.       
459
460    <Exceptions>
461       TypeError if a bad parameter is detected.
462       
463       ValueError if either "tries" or "wait" are negative integers
464    
465    <Side Effects>
466       None.
467
468    <Returns>
469       Returns (bool, status)  where bool is True on normal exit, False on 
470       error or interruption by a signal.
471       
472       Status is the exit status of the command, 1 on error or timeout 
473       
474       If the command returns fail_id then this function will be confused 
475       into thinking that it failed to execute.      
476    """
477    # check params
478    check_type_simple(command, "command", str, "system_timeout_backoff")
479    check_type_simple(tries, "tries", int, "system_timeout_backoff")
480    check_type_simple(wait, "wait", int, "system_timeout_backoff")
481    check_type_simple(kill_signal, "kill_signal", int, "system_timeout_backoff")
482
483    # make sure that variables "tries" and "wait" are non-negative
484    if tries < 0:
485        raise ValueError, 'The second argument must be a non-negative integer'
486    if wait < 0:
487        raise ValueError, 'The third argument must be a non-negative integer'
488    
489    # set up signal handler for child failure
490    global glo_child_failed 
491    glo_child_failed = False
492    signal.signal(signal.SIGUSR1, __child_failed_handler)   
493
494    time_start = time.time()
495
496    # give more than one try 
497    our_tries = tries
498    while our_tries > 0:  
499       
500       # if this is not the first pass
501       # multiply the current waiting time
502       # by two. this will cause the exponential
503       # backoff
504       if not our_tries == tries:
505           wait *= 2
506       print "trying for the "+str(tries-our_tries+1)+" time, waiting: "+str(wait)
507       
508       # call the system_timeout command with the
509       # specified command and wait time   
510       success, status = system_timeout(command, 1, wait)
511       
512       # if we succeeded then get out of this loop...
513       # else continue on
514       if success:
515           return (success, status)
516       
517       our_tries -= 1
518
519    # out of tries.. log failure
520    time_elapsed = round(time.time() - time_start, 3) # round to 3 decimal places
521    syslog.syslog("Error : system_timeout_backoff() Forced to abort command [waited " + str(time_elapsed) + " seconds] :" + command)
522    
523    return (False, 0)
524
525
526    
527
528 def system2(command):
529    """ 
530    <Purpose>
531       Runs a command and terminates the command after a predefined time.
532       Tries multiple times on failure.
533
534    <Arguments>
535       command:
536               Command to run.
537
538    <Exceptions>
539       TypeError if a bad parameter is detected.
540    
541    <Side Effects>
542       Terminates the program if the command fails.
543
544    <Returns>
545       Status of the run.  See system_timeout for details.
546    """
547    # check params
548    check_type_simple(command, "command", str, "system2")
549
550    # give 3 tries, wait 5 seconds each, if it times out: kill -9
551    (success, status) = system_timeout(command, 3, 5, 9) 
552    if not success:
553       # Command failed
554       os._exit(1)
555
556    return status
557
558
559
560
561
562 def fsystem2(comm, command):
563    """ 
564    <Purpose>
565       Runs a command and terminates the command after a predefined time.
566       Tries multiple times on failure.  Prints error messages to given
567       file stream.
568
569    <Arguments>
570       comm:
571               File stream to send error messages to.
572       command:
573               Command to run.
574
575    <Exceptions>
576       TypeError if a bad parameter is detected.
577    
578    <Side Effects>
579       Terminates the program if the command fails.
580
581    <Returns>
582       Status of the run.  See system_timeout for details.
583    """
584    # check params
585    check_type_simple(comm, "comm", file, "fsystem2")
586    check_type_simple(command, "command", str, "fsystem2")
587
588    # give 3 tries, wait 10 seconds each, if it times out: kill -9
589    (success, status) = system_timeout(command, 3, 10, 9) 
590    if not success:
591       # Command failed
592       try:
593          comm.sendall("Error: Command '" + command + "' timed out...")
594          comm.close()
595          syslog.syslog("Error: Command '" + command + "' timed out...")
596       except socket.error:
597          syslog.syslog("Error: arizonageneral.fsystem2(), Command '" + command + "' timed out (error reporting also failed)...")
598          syslog.syslog("Error : arizonageneral.fsystem2() comm call failed")
599       os._exit(1)
600
601    return status
602
603
604
605
606
607 def popen0(command):
608    """
609    <Purpose>
610       Runs a command, returning exit code.  The program must exit for this
611       function to return.
612    
613    <Arguments>
614       command: 
615               A string giving the command to run.
616       
617    <Exceptions>
618       None.
619    
620    <Side Effects>
621       None.
622
623    <Returns>
624       Program return status (exit code), or None.
625    """
626    # check params
627    check_type_simple(command, "command", str, "popen0")
628
629    # run command
630    return os.system(command + " &>/dev/null")
631
632
633
634
635
636 def popen5(command, tempdir="/tmp"):
637    """
638    <Purpose>
639       Runs a command, returning exit code, stdout, and stderr.  The 
640       program must exit for this function to return.
641    
642    <Arguments>
643       command: 
644               A string giving the command to run.
645       
646    <Exceptions>
647       IOError if an I/O error occurs (could not write to /tmp).
648    
649    <Side Effects>
650       None.
651
652    <Returns>
653       A tuple (out, err, status), containing stdout output, stderr output,
654       and the return status, respectively.
655    """
656    # check params
657    check_type_simple(command, "command", str, "popen5")
658
659    # create the tempdir if it doesn't exist
660    try:
661       os.makedirs(tempdir)
662    except OSError:
663       pass
664
665    fileout = os.path.join(tempdir, str(random.random()) + "popen5_stdout")
666    fileerr = os.path.join(tempdir, str(random.random()) + "popen5_stderr")
667    
668    # run command
669    status = os.system(command + " >" + fileout + " 2>" + fileerr)
670    
671    # get stdout
672    try:
673       f = file(fileout)
674       out = f.readlines()
675       f.close()
676       os.remove(fileout)
677    except OSError:
678       out = []
679    
680    # get stderr
681    try:
682       f = file(fileerr)
683       err = f.readlines()
684       f.close()
685       os.remove(fileerr)
686    except OSError:
687       err = []
688       
689    # fix status
690    if status == None:
691       # couldn't get a status code.. have to assume it's okay
692       status = 0
693    else:
694       # for some reason, status is multiplied by 256. we reverse that here
695       status = status >> 8         
696    
697    return (out, err, status)
698
699    
700
701
702
703 def popen6(command, tempdir="/tmp"):
704    """
705    <Purpose>
706       Runs a command, returning exit code, and stdout/stderr combined.  
707       The program must exit for this function to return.
708    
709    <Arguments>
710       command: 
711               A string giving the command to run.
712       
713    <Exceptions>
714       IOError if an I/O error occurs (could not write to /tmp).
715    
716    <Side Effects>
717       None.
718
719    <Returns>
720       A tuple (out, status), containing stdout/stderr output, and the 
721       return status, respectively.
722    """
723    # check params
724    check_type_simple(command, "command", str, "popen6")
725
726    # create the tempdir if it doesn't exist
727    try:
728       os.makedirs(tempdir)
729    except OSError:
730       pass
731
732    fileout = os.path.join(tempdir, str(random.random()) + "popen6_stdout")
733    
734    # run command
735    status = os.system(command + " &>" + fileout)
736    
737    # get stdout/stderr
738    try:
739       f = file(fileout)
740       out = f.readlines()
741       f.close()
742       os.remove(fileout)
743    except OSError:
744       out = []
745    
746    # fix status
747    if status == None:
748       # couldn't get a status code.. have to assume it's okay
749       status = 0
750    else:
751       # for some reason, status is multiplied by 256. we reverse that here
752       status = status >> 8         
753
754    return (out, status)
755
756
757
758
759
760 def split_quoted(text):
761    """
762    <Purpose>
763       Similar to split(), however double-quoted (") text is not split.  
764    
765    <Arguments>
766       text: 
767               The string to split.
768       
769    <Exceptions>
770       TypeError if a type mismatch or parameter error is detected.
771    
772    <Side Effects>
773       None.
774
775    <Returns>
776       A list containing text split by spaces (unless in double-quotes).
777
778    <Examples>
779       split_quoted('a b c') -> ['a', 'b', 'c']
780       split_quoted('a "b c"') -> ['a', 'b c']
781    """
782    # check params
783    check_type_simple(text, "text", str, "split_quoted")
784
785    split_list = []
786    quote_start = False
787    for piece in text.split('"'):
788       
789       # are we starting a quoted string?
790       if quote_start: 
791       
792          # yes, leave it alone
793          split_list.append(piece)
794       else:
795  
796          # no, break up all the spaces
797          split_list.extend(piece.split())
798          
799       # since we split by ", the next thing we will see is a quoted 
800       # string, or the end of a quoted string   
801       quote_start = not quote_start 
802    
803    return split_list
804
805
806    
807    
808    
809 def check_type(variable, parameter_name, expected_type, function_name):
810    """
811    <Purpose>
812       Checks the actual type of variable against the expected type, and
813       raises a TypeError if variable is of a type other than expected.
814
815    <Author>
816       Jeffry Johnston
817
818    <Arguments>    
819       variable:
820               Variable to check the type of.  Can be anything.
821       parameter_name:
822               Name of the function parameter being tested, as a string.  
823               Used for error reporting.
824       expected_type:
825               The expected variable type.  
826               
827                 Example (allow only integers): int  
828               
829               Can be a list, if the variable can be of multiple different 
830               types.  Only one type in the list must match for the entire 
831               list to match.  
832               
833                 Example (allow integers or strings): [int, str]
834               
835               A list can include other lists.  These sub-lists are 
836               slightly different in use than the previously mentioned 
837               list.  The first item in a sub-list must specify an iterable
838               type.  The variable will then be checked for an iterable 
839               of that type.  Multiple nesting of sub-lists is 
840               allowed.  
841               
842                 Example (require a list of strings): [[list, str]].  
843                 Note: Simply doing [list, str] will not result in the 
844                 desired check, because it will accept any list or any 
845                 string, as in the previous section. 
846
847                 Example (require a tuple containing lists of integers):
848                 [[tuple, [list, int]]]
849
850               Use the special string "iterable" to match any iterable 
851               type.                
852               
853                 Example (allow any iterable as long as it contains only
854                 strings): [["iterable", str]]
855                 
856                 Example: [None, "iterable"], allow None or any iterable 
857                 type to pass.  
858               
859               Use the special string "any" to allow any type, but not to 
860               to allow the absence of a type (this is useful to enforce 
861               that at least one item be in an iterable type such as a 
862               list).  
863               
864                 Example: [list, "any"].  This would match [3], but not [].
865                 Compare this to simply using: list, which would also match
866                 [].
867                 
868               Use the special string "empty" to allow an empty iterable 
869               type. 
870  
871                 Example: [[list, str, "empty"]].  Allows [], and ["abc"], 
872                 but not [[]], or [1].   
873               
874               The special type string "parent" auto-includes all of the 
875               types and sub-lists of the list parent.  The parent of the 
876               main list is None.  
877               
878                 Example: [int, [list, "parent"]].  This will accept
879                 any combination of integers and lists with integers in 
880                 them, for example: 1, [3], [[-5]], [10,[[7],[[[9]]],8]].  
881               
882               Special strings are case sensitive and must be given in 
883               lowercase.
884               
885               Other examples: file, int, list, [[list, "any"]], None, 
886               str, tuple, [type, None], [[list, str]], 
887               [["iterable", str]], [float, [tuple, "parent"]].     
888       function_name:
889               Name of the called function, as a string.  Used for error 
890               reporting.
891    
892    <Exceptions>
893       TypeError if a type mismatch or parameter error is detected.
894    
895    <Side Effects>
896       None.
897
898    <Returns>
899       None.
900    """
901    # first, check parameters passed to this function
902    __check_type_internal(expected_type, "expected_type", [type, str, None, ["iterable", "parent"]], "check_type")
903    check_type_simple(parameter_name, "parameter_name", str, "check_type")
904    check_type_simple(function_name, "function_name", str, "check_type")
905
906    # now perform requested check
907    __check_type_internal(variable, parameter_name, expected_type, function_name)
908
909
910
911
912
913 def __check_type_internal(variable, parameter_name, expected_type, function_name, parent=[None], depth=0):
914    """
915    <Purpose>
916       Checks the actual type of variable against the expected type, and
917       raises a TypeError if variable is of a type other than expected. 
918       Internal helper function to check_type that does the necessary 
919       recursive work (so this should never be called directly, use 
920       the function check_type instead).
921
922    <Author>
923       Jeffry Johnston
924
925    <Arguments>    
926       See check_type for full details.
927       parent:
928               Internal.  Must be None (default) for function to complete.
929               Used to know when the recursion is complete. 
930       depth:
931               Internal.  Used to indent debugging output.
932    
933    <Exceptions>
934       TypeError if a type mismatch or parameter error is detected.
935    
936    <Side Effects>
937       None.
938
939    <Returns>
940       True: found a match, False: didn't match
941    """      
942    # make expected_type a list if it wasn't already
943    type_list = expected_type
944    if not isinstance(type_list, list):
945       type_list = [ type_list ]
946       
947    # iterate over entries in type_list.  If "parent" is found, replace 
948    # it with the parent entry.  
949    temp = []
950    for item in type_list:
951       if item == "parent":
952          temp += parent
953       else:
954          temp.append(item)   
955    type_list = temp
956
957    # If they specify [] for the expected type, we need to catch that (fails),
958    # so the default behavior is to fail. 
959    answer = False
960    
961
962    # see if any of the patterns given in expected_type match the type of the variable
963    for expected_type in type_list:
964
965       # The type None doesn't actually exist, it is actually called NoneType.
966       # We specially allow None here (to be user-friendly). 
967       if expected_type == None:
968          answer = variable == None
969          
970       # Special string "parent".  Ignore this, it was already processed above.  
971       elif expected_type == "parent":
972          answer = False
973          
974       # Special string "any".  Always matches (passes). 
975       elif expected_type == "any":
976          answer = True
977          
978       # Special string "empty".  Ignore this, it was already processed by parent.
979       elif expected_type == "empty":
980          answer = False
981          
982       # Special string "iterable".  
983       elif expected_type == "iterable":
984          # is the type iterable?
985          answer = True
986          try:
987             iter(variable)
988          except TypeError:
989             answer = False
990
991       # Found a list.  It should be of the form [iterable type, ...]   
992       elif isinstance(expected_type, list):
993          
994          # Are there enough items in the list?  Need at least 2 to match the form.
995          if len(expected_type) < 2:
996             raise \
997                   TypeError, "A sub-list requires an iterable type and " + \
998                   "at least one expected type.  Function: '" + \
999                   function_name + "', specified '" + str(expected_type) + \
1000                   "' for expected_type."
1001
1002          # Is the first element of the expected type? 
1003          if expected_type[0] != "iterable":
1004            try: 
1005               is_inst = isinstance(variable, expected_type[0])
1006            except TypeError:
1007               raise \
1008                     TypeError, "Received a '" + \
1009                     str(type(expected_type[0])).split("'")[1] + "' (with value: " + \
1010                     str(expected_type[0]) + ") for the iterable type of the list '" + \
1011                     str(expected_type) + "'.  The iterable type must consist only of " + \
1012                     "iterable types or the special string \"iterable\" (case sensitive)."
1013          
1014          # special string "iterable", allows any iterable type
1015          # otherwise, variable needs to be of the type listed      
1016          if expected_type[0] == "iterable" or is_inst:
1017             
1018             # is the type iterable?
1019             try:
1020                iter(variable)
1021             except TypeError:
1022                answer = False
1023             else:
1024                # iterate over the variable, and recursively check each piece (item) of it
1025                if len(variable) == 0:
1026                   for item in expected_type[1:]:
1027                      if item == "empty":
1028                         answer = True
1029                else:
1030                   for item in variable:
1031                      answer = __check_type_internal(item, parameter_name, expected_type[1:], function_name, type_list, depth + 2)
1032                      # if we didn't have a match on this piece of the variable, 
1033                      # there is no reason to check the other pieces
1034                      if not answer:
1035                         break
1036          else:
1037             # fail, variable we were trying to match wasn't an interable type,
1038             # or wasn't the interable type that was expected
1039             answer = False         
1040       else:
1041          # This where non-iterable types such as int, str, float, are checked.
1042          # Note that this also checks list, tuple, etc, when specified as such,
1043          # (For example, if they didn't care what the list held, they could say
1044          # '[list, "any"]', or just simply 'list' if the list could be empty).  
1045          try: 
1046             answer = isinstance(variable, expected_type)
1047          except TypeError:
1048             raise \
1049                   TypeError, "Received a '" + \
1050                   str(type(expected_type)).split("'")[1] + "' (with value: " + \
1051                   str(expected_type) + ") for the parameter 'expected_type'.  " + \
1052                   "The parameter expected_type must consist only of " + \
1053                   "types, lists of types, or the special strings " + \
1054                   "\"iterable\", \"parent\", or \"any\" (case sensitive)."
1055          
1056       # If we found a match, then we're done with all of this.. break out   
1057       if answer:
1058          break
1059
1060    # If this is the top level, then a failure generates a TypeError so 
1061    # that the user may see it.  Otherwise, we're not done yet, so the 
1062    # True / False result is returned to the parent caller to deal with.  
1063    if parent == [None]:
1064       if not answer:
1065          raise \
1066                TypeError, "Incorrect type passed to parameter '" + \
1067                parameter_name + "' of function '" + \
1068                function_name + "'.  Received the type: '" + \
1069                str(type(variable)).split("'")[1] + "' (with value: " + \
1070                str(variable) + \
1071                "), but expected to receive a type matching the pattern: '" + \
1072                str(type_list) + "'."
1073       else:
1074          return True
1075    else:
1076       return answer
1077
1078
1079
1080
1081
1082 def check_types(list, function, modulename=None):
1083    """
1084    <Purpose>
1085       Pass this method a list of data,type pairs of args your function 
1086       takes along with your function as the second parameter, and this 
1087       method will attempt to determine the full name of the function, and 
1088       the names of the variables and pass the information to the 
1089       check_type method. (note: this will skip over the self parameter for
1090       class methods)  Using this method could also have the added benefit 
1091       of making a method slightly more portable.  The method could be
1092       moved or copied with very little change, if any.
1093    <Author>
1094       Jason Hardies
1095    <Arguments>
1096       list:
1097          a list of variable,type pairs (see check_type)
1098       function:
1099          the function to use
1100       modulename: (optional)
1101          The name to insert as the module name (specifically for use in 
1102          the case where the method is in a main module)
1103    <Exceptions,etc.>
1104       See check_type.
1105    <Example>
1106       class Test:
1107          def myfunc(self, a_str, b_str, c_int):
1108             check_types([[a_str,str],[b_str,str],[c_int,int]],self.myfunc)
1109             #should be the same as:
1110             check_type(a_str,'a_str',str,'a_module.Test.myfunc')
1111             check_type(b_str,'b_str',str,'a_module.Test.myfunc')
1112             check_type(c_int,'c_int',int,'a_module.Test.myfunc')
1113    <Note>
1114       It is recommended that you use the class name instead of self for 
1115       class methods (unlike the example above), in case of inheritance 
1116       (eg: calling the init method of the parent class).
1117    """
1118    arglist = inspect.getargspec(function)[0]
1119    
1120    # try to divine the full method/function name:
1121    # note: this should get all the lib.lib.lib.s that might be in the 
1122    #       module name, but will use the actual module's name
1123    # also, for the main script, this will be __main__
1124    function_name = function.__module__ + "."
1125    if modulename:
1126       function_name = modulename + "."
1127    if type(function) == types.MethodType:
1128       # all class methods are of type method, methods/functions not inside
1129       # of a class are the function type.
1130       # since it has a class, we're assuming the first argument (whatever 
1131       # it may be named), is the self argument)
1132       arglist = arglist[1:]
1133       function_name += function.im_class.__name__ + '.'
1134    function_name += function.__name__
1135    for i in range(len(arglist)):
1136       check_type(list[i][0], arglist[i], list[i][1], function_name)   
1137    
1138    
1139    
1140    
1141    
1142 def check_type_simple(variable, parameter_name, expected_type, function_name, noneok=False):
1143    """
1144    <Purpose>
1145       Checks the actual type of variable against the expected type, and
1146       raises a TypeError if variable is of a type other than expected.
1147       This function accepts only a limited amount of functionality in
1148       order to increase its speed.  Does not check its own input, so must
1149       be called correctly.
1150
1151    <Author>
1152       Jeffry Johnston
1153
1154    <Arguments>    
1155       variable:
1156               Variable to check the type of.  Can be anything.
1157       parameter_name:
1158               Name of the function parameter being tested, as a string.  
1159               Used for error reporting.
1160       expected_type:
1161               The expected variable type (int, str, dict, ...)  
1162       function_name:
1163               Name of the called function, as a string.  Used for error 
1164               reporting.
1165    
1166    <Exceptions>
1167       TypeError if a type mismatch or parameter error is detected.
1168    
1169    <Side Effects>
1170       None.
1171
1172    <Returns>
1173       None.
1174    """
1175    if not isinstance(variable, expected_type) and (not noneok or variable != None):
1176       raise TypeError, "Incorrect type passed to parameter '" + \
1177             parameter_name + "' of function '" + \
1178             function_name + "'.  Received the type: '" + \
1179             str(type(variable)).split("'")[1] + "' (with value: " + \
1180             str(variable) + \
1181             "), but expected to receive a type matching the pattern: '" + \
1182             str(expected_type) + "'."
1183
1184
1185
1186
1187
1188 def check_type_stringlist(variable, parameter_name, function_name):
1189    """
1190    <Purpose>
1191       Checks the actual type of variable to see if it is a list containing
1192       either nothing, or containing strings (or unicodes) only.  Raises a 
1193       TypeError if variable is of a type other than expected.  Does not 
1194       check its own input, so must be called correctly.
1195
1196    <Author>
1197       Jeffry Johnston
1198
1199    <Arguments>    
1200       variable:
1201               Variable to check the type of.  Can be anything.
1202       parameter_name:
1203               Name of the function parameter being tested, as a string.  
1204               Used for error reporting.
1205       function_name:
1206               Name of the called function, as a string.  Used for error 
1207               reporting.
1208    
1209    <Exceptions>
1210       TypeError if a type mismatch or parameter error is detected.
1211    
1212    <Side Effects>
1213       None.
1214
1215    <Returns>
1216       None.
1217    """
1218    # is it a list?
1219    if not isinstance(variable, list):
1220       raise TypeError, "Incorrect type passed to parameter '" + \
1221             parameter_name + "' of function '" + \
1222             function_name + "'.  Received the type: '" + \
1223             str(type(variable)).split("'")[1] + "' (with value: " + \
1224             str(variable) + \
1225             "), but expected to receive a string list."
1226       
1227    # empty list?   
1228    if not variable:
1229       return 
1230
1231    # iterate over values, check for strings
1232    for item in variable:
1233       if not isinstance(item, str) and not isinstance(item, unicode):
1234          raise TypeError, "Incorrect type passed to parameter '" + \
1235                parameter_name + "' of function '" + \
1236                function_name + "'.  Received the type: '" + \
1237                str(type(variable)).split("'")[1] + "' (with value: " + \
1238                str(variable) + \
1239                "), but expected to receive a string list."
1240
1241
1242
1243
1244 def check_running(program):
1245    """
1246    <Purpose>
1247       Checks to see if a deamon is already running
1248
1249    <Arguments>
1250       program:
1251               Filename (not including path) of the program being turned
1252               into a daemon.  Used to
1253               read the pid of the daemon from `/var/run/PROGRAM.pid'.
1254
1255    <Exceptions>
1256       TypeError if a bad parameter is detected.
1257
1258    <Returns>
1259       pid of the daemon or -1 if no daemon is running
1260    """
1261    filename = "/var/run/" + program + ".pid"
1262    try:
1263       in_file = open(filename, "w")
1264       pid = int(in_file.read())
1265       in_file.close()
1266    except IOError:
1267       syslog.syslog("[" + program + "] IOError reading: " + filename)
1268       return -1
1269
1270    result = os.system("ps -p " + str(pid) + " > /dev/null")
1271
1272    # XXX: how do we know the pid is the daemon we are interested in, and
1273    #   not that our demon terminated and some other program is running?
1274
1275    if result == 0:
1276       # the process exists
1277       return pid
1278    else:
1279       # the process does not exist
1280       return -1
1281
1282
1283
1284
1285
1286 def make_daemon(program):
1287    """
1288    <Purpose>
1289       Turns the currently running program into a daemon, detaching it from
1290       the console so that it runs in the background.
1291
1292    <Arguments>
1293       program:
1294               Filename (not including path) of the program being turned
1295               into a daemon.  Used for logging, error reporting, and to 
1296               write the pid of the daemon to `/var/run/PROGRAM.pid'.
1297
1298    <Exceptions>
1299       TypeError if a bad parameter is detected.
1300    
1301    <Side Effects>
1302       Terminates the program if the command fails.
1303
1304    <Returns>
1305       pid of the daemon
1306    """
1307    # check params
1308    check_type_simple(program, "program", str, "make_daemon")
1309    
1310    # log what we're about to do and fork
1311    syslog.syslog("[" + program + "] Starting daemon...")
1312    pid = os.fork()
1313
1314    # if fork was successful, exit the parent process so it returns 
1315    try:
1316       if pid > 0:
1317          os._exit(0) 
1318    except OSError:
1319       syslog.syslog("[" + program + "] Error: fork failed, daemon not started")
1320       sys.exit(1)
1321
1322    # Print my pid into /var/run/PROGRAM.pid
1323    pid = str(os.getpid())
1324    filename = "/var/run/" + program + ".pid"
1325    try:
1326       out_file = open(filename, "w")
1327       out_file.write(pid)
1328       out_file.close()
1329    except IOError:
1330       syslog.syslog("[" + program + "] IOError writing: " + filename)
1331
1332    # close any open files
1333    try:
1334       sys.stdin.close()
1335    except:
1336       syslog.syslog("[" + program + "] Error closing stdin")
1337    try:
1338       sys.stdout.close()
1339    except:
1340       syslog.syslog("[" + program + "] Error closing stdout")
1341    try:
1342       sys.stderr.close()
1343    except:
1344       syslog.syslog("[" + program + "] Error closing stderr")
1345    for i in range(1023):
1346       try:
1347          os.close(i)
1348       except OSError:
1349          pass
1350          
1351    # redirect stdin/out/err to /dev/null    
1352    try:
1353       sys.stdin = open('/dev/null')       # fd 0
1354    except:
1355       syslog.syslog("[" + program + "] Error opening new stdin")
1356    try:
1357       sys.stdout = open('/dev/null', 'w') # fd 1
1358    except:
1359       syslog.syslog("[" + program + "] Error opening new stdout")
1360    try:
1361       sys.stderr = open('/dev/null', 'w') # fd 2  
1362    except:
1363       syslog.syslog("[" + program + "] Error opening new stderr")
1364
1365    # disassociate from parent
1366    os.chdir("/")
1367    os.setsid()
1368    os.umask(0)
1369
1370    return pid
1371
1372
1373
1374
1375
1376 def mutex_lock(program, lockdir="/var/lock", unlock_on_exit=True):
1377    """
1378    <Purpose>
1379       Checks to see if we can obtain a mutex lock (i.e. is there only one
1380       copy of the specified program running?), if not, returns False;
1381       otherwise, sets a mutex lock and returns True.
1382
1383    <Arguments>
1384       program:
1385               Name of the program.
1386       lockdir: (default: "/var/lock")
1387               Directory to store the lock file in.
1388       unlock_on_exit: (default: True)
1389               Whether the mutex lock should automatically be unlocked on
1390               program exit.
1391
1392    <Exceptions>
1393       None.
1394
1395    <Side Effects>
1396       None.
1397
1398    <Returns>
1399       None if lock cannot be obtained
1400       an open file descriptor for the lock if lock can be obtained 
1401    """
1402    # Justin: we do this here so that this is imported only in applications that
1403    # need it
1404    import fcntl
1405
1406    # check params
1407    check_type_simple(program, "program", str, "mutex_lock")
1408    check_type_simple(lockdir, "lockdir", str, "mutex_lock")
1409    check_type_simple(unlock_on_exit, "unlock_on_exit", bool, "mutex_lock")
1410
1411    # create the lockdir if it doesn't exist
1412    try:
1413       os.makedirs(lockdir)
1414    except OSError:
1415       pass
1416
1417    # open the lock file for writing
1418    # we should always be able to open the file, even if it is locked.
1419    lock = file(lockdir + "/" + program + ".lock", "w")
1420
1421    try:
1422       # attempt to obtain the file lock
1423       fcntl.flock(lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
1424    except IOError:
1425       # lock not obtained, abort
1426       lock.close()
1427       return None
1428
1429    #print "locked " + program
1430
1431    # obtained the lock
1432    if unlock_on_exit:
1433       atexit.register(mutex_unlock, lock)
1434    return lock
1435
1436
1437
1438 def mutex_unlock(lock):
1439    """
1440    <Purpose>
1441       Releases the mutex lock (if any).
1442
1443    <Arguments>
1444       None
1445
1446    <Exceptions>
1447       None.
1448
1449    <Side Effects>
1450       None.
1451
1452    <Returns>
1453       True or False.
1454    """
1455    # Justin: we do this here so that this is imported only in applications that
1456    # need it
1457    import fcntl
1458
1459    if lock != None:
1460       # if the lock file is closed, then we already released it.
1461       # if it is still open, then we are still holding the lock and need to
1462       # release and delete it.
1463       if not lock.closed:
1464          filename = lock.name
1465          fcntl.flock(lock, fcntl.LOCK_UN)
1466          lock.close()
1467          if os.path.isfile(filename):
1468             os.remove(filename)
1469          #print "unlocked " + str(lock)
1470
1471
1472
1473
1474
1475 class Exception_Data(Exception):
1476    """
1477    <Purpose>
1478       Exception with an extra data field.
1479
1480    <Parent>
1481       Exception
1482    """
1483    message = None
1484    data = None
1485
1486    def __init__(self, message, data):
1487       """
1488       <Purpose>
1489          Exception with string list.
1490
1491       <Arguments>
1492          message:
1493                  Error message.
1494          data:
1495                  Error data (tuple, list, etc).
1496
1497       <Exceptions>
1498          None.
1499
1500       <Side Effects>
1501          None.
1502
1503       <Returns>
1504          None.
1505       """
1506       Exception.__init__(self)
1507       self.message = message
1508       self.data = data
1509
1510
1511
1512
1513
1514 def list_to_args(raw_list):
1515    """
1516    <Purpose>
1517       Converts a list of command line parameters into a space separated 
1518       list, with special characters escaped for command-line use.  Will
1519       have a leading space unless len(raw_list) == 0.
1520
1521    <Arguments>
1522       raw_list:
1523               List of strings each contaning a command line argument.
1524
1525    <Exceptions>
1526       TypeError:
1527               If a type mismatch or parameter error is detected.
1528
1529    <Side Effects>
1530       None.
1531
1532    <Returns>
1533       String containing space separated args.
1534    """
1535    # check params
1536    check_type_stringlist(raw_list, "raw_list", "list_to_args")
1537    
1538    args = ""
1539    for item in raw_list:
1540       args += " " + re.escape(item)
1541    return args
1542
1543
1544
1545
1546
1547 def gethostname():
1548    """ Returns hostname, or None on error """
1549    hostname = None   
1550    try:
1551       import socket
1552    except ImportError:
1553       pass
1554    try:
1555       hostname = socket.gethostname()
1556    except NameError:
1557       pass
1558    
1559    if hostname == None:   
1560       try:
1561          import os
1562       except ImportError:
1563          pass
1564       try:
1565          nodehost = os.getenv("HOSTNAME", None)
1566       except NameError:
1567          hostname = None
1568          
1569    return hostname      
1570
1571
1572
1573 def getslicename(default=None):
1574    try:
1575       if os.path.isfile("/etc/slicename"):
1576          slicefile = open("/etc/slicename")
1577          slicename = slicefile.read().strip()
1578          slicefile.close()
1579       else:
1580          return default
1581    except (NameError, IOError):
1582       return default
1583
1584    return slicename
1585
1586
1587
1588    
1589 def getusername():
1590    """ TODO comments """
1591    # refactored 05Feb2007 by Jeffry Johnston to fix very ugly changes that  
1592    # have been made since my original code was written
1593    try:
1594       import os
1595    except ImportError:
1596       pass
1597    try:
1598       username = os.getlogin()
1599    except (NameError, OSError):
1600       username = None
1601       
1602    if username == None or username == "root":
1603       try:
1604          username = os.environ.get("USER")
1605       except (NameError, OSError):
1606          username = None   
1607    else:
1608       return username
1609             
1610    if username == None or username == "root":
1611       try:
1612          username = os.environ.get("SUDO_USER")
1613       except (NameError, OSError):
1614          username = None 
1615    else:
1616       return username
1617
1618    if username == None or username == "root":
1619       try:
1620          username = os.environ.get("USERNAME")
1621       except (NameError, OSError):
1622          username = None 
1623    else:
1624       return username
1625
1626    if username == None or username == "root":
1627       try:
1628          username = os.environ.get("LOGNAME")
1629       except (NameError,OSError):
1630          username = None 
1631    else:
1632       return username
1633
1634    if username == None or username == "root":
1635       try:
1636          tmp_in, tmp_out, tmp_err = os.popen3("/usr/bin/id -un")
1637          tmp_in.close()
1638          tmp_str = "".join(tmp_err.readlines()).strip()
1639          tmp_err.close()
1640          if tmp_str != "":
1641             username = None
1642          else:
1643             username = "".join(tmp_out.readlines()).strip()
1644          tmp_out.close()
1645       except (IOError, OSError, NameError):
1646          username = None 
1647    else:
1648       return username
1649
1650    # JRP - 112706
1651    # last fail over method
1652    # if username is still root or None at this point, try to read the
1653    # /etc/slicename file
1654    if username == None or username == "root":
1655       try:
1656          if os.path.isfile("/etc/slicename"):
1657             slicefile = open("/etc/slicename")
1658             username = slicefile.read().strip()    
1659             slicefile.close()
1660          elif username == None:
1661             username = "UNKNOWN USERNAME" 
1662       except (NameError, IOError):
1663          if username == None or username == "":  
1664             username = "UNKNOWN USERNAME" 
1665
1666    return username
1667
1668
1669
1670
1671
1672 def uniq_string(s):
1673    """
1674    <Purpose>
1675       Remove duplicate lines from a string. Lines in the string are seperated by
1676       newline characters. Duplicate lines are marked with "previous line
1677       repeated n times".
1678
1679    <Arguments>
1680       s:
1681          string to remove duplicates
1682    <Returns>
1683       string with duplicates removed
1684    """
1685    orig_list = s.split("\n")
1686    new_list = []
1687
1688    last = None
1689    last_count = 0
1690
1691    for line in orig_list:
1692        if line == last:
1693           last_count = last_count + 1
1694        else:
1695           if last_count == 1:
1696              # if we only have a single duplicate line, then just output the
1697              # duplicate line. This avoids printing the 'repeat' message
1698              # for minor things like when multiple newlines occur.
1699              new_list.append(line)
1700           elif last_count >= 2:
1701              new_list.append("previous line repeated " + str(last_count) + " times")
1702           last_count = 0
1703           new_list.append(line)
1704           last = line
1705
1706    # catch the case where the last line of the string was a duplicate
1707    if last_count > 0:
1708       new_list.append("previous line repeated " + str(last_count+1) + " times")
1709
1710    return "\n".join(new_list)
1711
1712
1713
1714
1715
1716 def valid_fn(filename):
1717    """
1718    <Purpose>
1719       This returns True if filename is a valid file that may be opened for
1720       reading or False if it is not.
1721
1722    <Arguments>
1723       filename:
1724           The name of the file to be checked
1725
1726    <Exceptions>
1727       None
1728
1729    <Side Effects>
1730       None
1731
1732    <Returns>
1733       True or False (see above)
1734    """
1735    # Verify that filename is a valid string 
1736    if isinstance(filename, str) or isinstance(filename, unicode):
1737
1738       # If the path is not valid
1739       if not os.path.exists(filename):
1740          return False
1741
1742       # If filename does not refer to a file
1743       # TODO FIXME /dev/null isn't a file
1744       #if not os.path.isfile(filename):
1745       #   print "b"
1746       #   return False
1747
1748       # Try to open the file for reading
1749       try:
1750          f = open(filename, "r")
1751          f.close()
1752       except IOError:
1753          return False
1754
1755       return True
1756
1757    else:
1758       # filename wasn't even a string...
1759       return False
1760
1761
1762
1763
1764
1765 def text_replace_files_in_fnlist(find, replace, fn_list):
1766    """
1767    <Purpose>
1768       Performs a find and replace in a list of files
1769
1770    <Arguments>
1771       find:
1772               This is the string that should be located in each file
1773       replace:
1774               This should be substituted for the find string
1775       fn_list:
1776               This is the list of files to be examined
1777
1778    <Exceptions>
1779       TypeError if the arguments are invalid
1780
1781    <Side Effects>
1782       Changes the file contents on disk as described
1783
1784    <Returns>
1785       A tuple with a boolean that indicates if the operation was performed and 
1786       a list of files that failed.   This function will not modify any files 
1787       if it returns False (even the correctly specified files in the list).
1788    """
1789    check_type(find, "find", [str, unicode], "text_replace_files_in_fnlist")
1790    check_type(replace, "replace", [str, unicode], "text_replace_files_in_fnlist")
1791    check_type_stringlist(fn_list, "fn_list", "text_replace_files_in_fnlist")
1792
1793    # Check each filename to see if it is valid
1794    bad_fns = []
1795    for thisfn in fn_list:
1796       if not valid_fn(thisfn):
1797          bad_fns.append(thisfn)
1798
1799    # Some file names are invalid, return False and a list
1800    if bad_fns:
1801       return (False, bad_fns)
1802    
1803    # Perform the replace
1804    for filename in fn_list:
1805       a_file = open(filename, "r+")
1806       text = a_file.read().replace(find, replace)
1807       a_file.seek(0)
1808       a_file.write(text)
1809       a_file.truncate()
1810       a_file.close()
1811
1812    return (True, [])
1813    
1814
1815
1816
1817
1818 def text_replace_files_in_fnlist_re(find, replace, fn_list):
1819    """
1820    <Purpose>
1821       Performs a find and replace in a list of files using a regular expression.
1822
1823    <Arguments>
1824       find:
1825               This is the regular expression that should be located in each file
1826       replace:
1827               This should be substituted for the find string
1828       fn_list:
1829               This is the list of files to be examined
1830
1831    <Exceptions>
1832       TypeError if the arguments are invalid
1833
1834    <Side Effects>
1835       Changes the file contents on disk as described
1836
1837    <Returns>
1838       A tuple with a boolean that indicates if the operation was performed and 
1839       a list of files that failed.   This function will not modify any files 
1840       if it returns False (even the correctly specified files in the list).
1841    """
1842    check_type(find, "find", [str, unicode], "text_replace_files_in_fnlist")
1843    check_type(replace, "replace", [str, unicode], "text_replace_files_in_fnlist")
1844    check_type_stringlist(fn_list, "fn_list", "text_replace_files_in_fnlist")
1845
1846    # Check each filename to see if it is valid
1847    bad_fns = []
1848    for thisfn in fn_list:
1849       if not valid_fn(thisfn):
1850          bad_fns.append(thisfn)
1851
1852    # Some file names are invalid, return False and a list
1853    if bad_fns:
1854       return (False, bad_fns)
1855    
1856    # Perform the replace
1857    cfind=re.compile(find,re.DOTALL)
1858    for filename in fn_list:
1859       a_file = open(filename, "r+")
1860       text = cfind.sub(replace,a_file.read())
1861       a_file.seek(0)
1862       a_file.write(text)
1863       a_file.truncate()
1864       a_file.close()
1865
1866    return (True, [])
1867
1868
1869
1870
1871
1872 def remote_popen(hostname, command):
1873    """
1874    <Purpose>
1875       Executes a command on a remote node and returns the error and output 
1876       results.   Pipes, redirection, etc. should be handled correctly by
1877       this function.
1878
1879    <Arguments>
1880       hostname:
1881               The host to execute the command on
1882       command:
1883               The command to be executed (may contain spaces, etc.)
1884
1885    <Exceptions>
1886       IOError if the host cannot be located or there is an error with the 
1887       command
1888
1889    <Side Effects>
1890       Nothing other than what command does
1891
1892    <Returns>
1893       A tuple containing the standard out and standard error as a list of 
1894       strings.   
1895    """
1896    # Justin: we do this here so that this is imported only in applications that
1897    # need it
1898    import fcntl
1899
1900    check_type(hostname, "hostname", [str, unicode], "remote_popen")
1901    check_type(command, "command", [str, unicode], "remote_popen")
1902
1903    (cmd_in, cmd_out, cmd_err) = os.popen3("ssh "+hostname+" "+re.escape(command))
1904
1905    # Close the input stream
1906    cmd_in.close()
1907
1908    # Make stderr and stdout non-blocking so that it doesn't deadlock
1909    flags = fcntl.fcntl(cmd_out.fileno(), fcntl.F_GETFL)
1910    fcntl.fcntl(cmd_out.fileno(), fcntl.F_SETFL, flags | os.O_NDELAY)
1911
1912    flags = fcntl.fcntl(cmd_err.fileno(), fcntl.F_GETFL)
1913    fcntl.fcntl(cmd_err.fileno(), fcntl.F_SETFL, flags | os.O_NDELAY)
1914
1915    # I need to use select because of blocking issues if a stream gets too full
1916    out_str = ''
1917    err_str = ''
1918
1919    streams = [ cmd_out, cmd_err ] 
1920    # Read until they are both at eof
1921    while streams:
1922       (read_streams, junk_write_streams, junk_event_streams) = select.select(streams, [], [], 0)
1923
1924       if cmd_out in read_streams:
1925          outdata = cmd_out.read()
1926          out_str = out_str + outdata
1927          # I'm finished with the output so close the stream.
1928          if outdata == '':
1929             streams.remove(cmd_out)
1930             cmd_out.close()
1931
1932       if cmd_err in read_streams:
1933          errdata = cmd_err.read()
1934          err_str = err_str + errdata
1935          # I'm finished with the error so close the stream.
1936          if errdata == '':
1937             streams.remove(cmd_err)
1938             cmd_err.close()
1939
1940       time.sleep(0.1)
1941
1942    # I split at \n, \r, etc to make the items string lists
1943    out_list = out_str.splitlines()
1944    err_list = err_str.splitlines()
1945
1946    return (out_list, err_list)
1947    
1948
1949
1950
1951
1952 def stream_to_sl(in_stream):
1953    """
1954    <Purpose>
1955       This returns the string list it reads from a given stream. 
1956       If an error occurs, the routine simply returns the exception.
1957
1958    <Arguments>
1959       in_stream:
1960             This is a stream that the string list will be extraced from.
1961
1962    <Exceptions>
1963       IOError and ValueError as may be returned by file.readline() or 
1964       TypeError if in_stream is not a file object (stream)
1965
1966    <Side Effects>
1967       None
1968
1969    <Returns>
1970       True or False (see above)
1971    """
1972    # Make sure we're given a file as an argument...
1973    if not isinstance(in_stream, file):
1974       raise TypeError, "Invalid stream in stream_to_sl"
1975
1976    # Start with an empty list
1977    ret_sl = []
1978
1979    # add lines to the list (after removing the \r\n characters)
1980    rawinput = in_stream.readline()
1981    while rawinput:
1982       ret_sl.append(rawinput.strip('\r\n'))
1983       rawinput = in_stream.readline()
1984
1985    # return the sl
1986    return ret_sl
1987
1988
1989
1990
1991
1992 def get_main_module_path():
1993    """
1994    <Purpose>
1995       Returns the directory where the main program module exists on disk.
1996       Should be relatively smart, for example, if the user typed 
1997       ./stork.py, this should still return /usr/local/stork/bin.  Also 
1998       works from the python shell.  Also works if the program is in the
1999       system path.
2000
2001    <Arguments>
2002       None.
2003
2004    <Exceptions>
2005       None.
2006
2007    <Side Effects>
2008       None
2009
2010    <Returns>
2011       Directory where the main module exists on disk.
2012    """
2013    program = sys.argv[0]
2014    path = os.path.realpath(program)
2015    if program != "":
2016       path = os.path.split(path)[0]
2017    return path
2018    
2019    
2020
2021
2022
2023 def rmdir_recursive(path):
2024    """
2025    <Purpose>
2026       Removes the directory, and all its files and subdirectories.  
2027       Similar to rm -rf, but does not use an os.system call.
2028
2029    <Arguments>
2030       path:
2031          Directory to remove.
2032
2033    <Exceptions>
2034       None.
2035
2036    <Side Effects>
2037       None.
2038
2039    <Returns>
2040       True on success (all files/dirs removed), False on error (some or 
2041       all of the files/dirs remain).
2042    """
2043    check_type_simple(path, "path", str, "rmdir_recursive")
2044
2045    success = True
2046
2047    # generate directory and file listing
2048    try:
2049       walk = os.walk(path)
2050    except OSError:
2051       return False
2052
2053    # make a list of directories and remove files
2054    dirs = []
2055    for root, junk, files in os.walk(path):
2056       dirs.append(root)
2057       for filename in files:
2058          try:
2059             os.remove(os.path.join(root, filename))
2060          except OSError:
2061             success = False   
2062          
2063    # remove the directories (in reverse order, so subdirectories are
2064    # removed before their parents)  
2065    dirs.reverse()
2066    for directory in dirs:
2067       try:
2068          os.rmdir(directory)
2069       except OSError:
2070          success = False
2071    
2072    return success
2073    
2074
2075
2076
2077
2078 def grep_escape(s, special_star=False):
2079    """
2080    <Purpose>
2081       Escapes a line for use with a grep system call.
2082       
2083    <Arguments>
2084       s:
2085          String to be escaped.
2086       
2087       special_star:   
2088          If True, any * (asterisk) in s wil become .* (a wildcard match),
2089          otherwise False indicates the stars wil be escaped and have no
2090          special meaning.
2091
2092    <Exceptions>
2093       None.
2094
2095    <Side Effects>
2096       None.
2097
2098    <Returns>
2099       The grep-escaped string.
2100    """
2101    check_type_simple(s, "s", str, "grep_escape")
2102
2103    o = ""
2104    for c in s:
2105       if c == "<" or c == ">" or c == "|" or c == "(" or c == ")" or \
2106          c == "=" or c == ";" or c == "!" or c == "`" or c =="?" or \
2107          c == "&" or c == "'" or c == " " or c == "\"":
2108          o += "\\"
2109       elif c == "[" or c == "]" or c == "^" or c == "." or c == "$":  
2110          # "*" would be in this category, but it is a special case that 
2111          # is handled separately below
2112          o += "\\\\"
2113       elif c == "*":
2114          if special_star:
2115             # convert * to .*
2116             o += "."   
2117          else:
2118             o += "\\\\"   
2119       elif c == "\\":
2120          # only \\\ here because the 4th will be added below
2121          o += "\\\\\\"
2122       o += c
2123    return o
2124    
2125
2126
2127
2128
2129 def program_exists(name):
2130    """
2131    <Purpose>
2132       Checks the path for the presence of a file (hopefully executable)
2133
2134    <Arguments>
2135       name:
2136          Program name
2137
2138    <Exceptions>
2139       None.
2140
2141    <Side Effects>
2142       None.
2143
2144    <Returns>
2145       True if the program was found in the path, False otherwise
2146    """
2147    check_type_simple(name, "name", str, "program_exists")
2148    path = os.environ.get("PATH").split(os.pathsep)
2149    for loc in path:
2150       if os.path.isfile(os.path.join(loc, name)):
2151          return True
2152    return False
2153
2154
2155
2156
2157
2158 def makedirs_existok(name):
2159    """
2160    <Purpose>
2161       Creates a directory, but does not throw an exception if it already
2162       exists.
2163
2164    <Arguments>
2165       name:
2166          directory
2167
2168    <Exceptions>
2169       None.
2170
2171    <Side Effects>
2172       None.
2173
2174    <Returns>
2175       None.
2176    """
2177    try:
2178       os.makedirs(name)
2179    # if the path already exists then pass
2180    except OSError, (errno, strerr):
2181       if errno == 17:
2182          pass
2183
2184
2185
2186 def lcut(str, substr):
2187    if str.startswith(substr):
2188       str = str[len(substr):]
2189    return str