1768c1302SMichael Walsh#!/usr/bin/wish 2768c1302SMichael Walsh 3*410b1787SMichael Walsh# This file provides many valuable validation procedures such as valid_value, valid_integer, etc. 4768c1302SMichael Walsh 5768c1302SMichael Walshmy_source [list print.tcl call_stack.tcl] 6768c1302SMichael Walsh 7768c1302SMichael Walsh 8768c1302SMichael Walshproc valid_value { var_name { invalid_values {}} { valid_values {}} } { 9768c1302SMichael Walsh 10*410b1787SMichael Walsh # If the value of the variable named in var_name is not valid, print an error message and exit the program 11*410b1787SMichael Walsh # with a non-zero return code. 12768c1302SMichael Walsh 13768c1302SMichael Walsh # Description of arguments: 14*410b1787SMichael Walsh # var_name The name of the variable whose value is to be validated. 15*410b1787SMichael Walsh # invalid_values A list of invalid values. If the variable value is equal to any value in 16*410b1787SMichael Walsh # the invalid_values list, it is deemed to be invalid. Note that if you 17*410b1787SMichael Walsh # specify anything for invalid_values (below), the valid_values list is not 18*410b1787SMichael Walsh # even processed. In other words, specify either invalid_values or 19*410b1787SMichael Walsh # valid_values but not both. If no value is specified for either 20*410b1787SMichael Walsh # invalid_values or valid_values, invalid_values will default to a list 21*410b1787SMichael Walsh # with one blank entry. This is useful if you simply want to ensure that 22*410b1787SMichael Walsh # your variable is non blank. 23*410b1787SMichael Walsh # valid_values A list of invalid values. The variable value must be equal to one of the 24*410b1787SMichael Walsh # values in this list to be considered valid. 25768c1302SMichael Walsh 26*410b1787SMichael Walsh # Call get_stack_var_level to relieve the caller of the need for declaring the variable as global. 27768c1302SMichael Walsh set stack_level [get_stack_var_level $var_name] 28768c1302SMichael Walsh # Access the variable value. 29768c1302SMichael Walsh upvar $stack_level $var_name var_value 30768c1302SMichael Walsh 31768c1302SMichael Walsh set len_invalid_values [llength $invalid_values] 32768c1302SMichael Walsh set len_valid_values [llength $valid_values] 33768c1302SMichael Walsh 34768c1302SMichael Walsh if { $len_valid_values > 0 && $len_invalid_values > 0 } { 35768c1302SMichael Walsh append error_message "Programmer error - You must provide either an" 36768c1302SMichael Walsh append error_message " invalid_values list or a valid_values" 37768c1302SMichael Walsh append error_message " list but NOT both.\n" 38768c1302SMichael Walsh append error_message [sprint_list invalid_values "" "" 1] 39768c1302SMichael Walsh append error_message [sprint_list valid_values "" "" 1] 40768c1302SMichael Walsh print_error_report $error_message 41768c1302SMichael Walsh exit 1 42768c1302SMichael Walsh } 43768c1302SMichael Walsh 44fdb4d995SMichael Walsh set caller [get_stack_proc_name -2] 45fdb4d995SMichael Walsh if { $caller == "valid_list" } { 46fdb4d995SMichael Walsh set exit_on_fail 0 47fdb4d995SMichael Walsh } else { 48fdb4d995SMichael Walsh set exit_on_fail 1 49fdb4d995SMichael Walsh } 50768c1302SMichael Walsh if { $len_valid_values > 0 } { 51768c1302SMichael Walsh # Processing the valid_values list. 52768c1302SMichael Walsh if { [lsearch -exact $valid_values "${var_value}"] != -1 } { return } 53768c1302SMichael Walsh append error_message "The following variable has an invalid value:\n" 54768c1302SMichael Walsh append error_message [sprint_varx $var_name $var_value "" "" 1] 55768c1302SMichael Walsh append error_message "\nIt must be one of the following values:\n" 56768c1302SMichael Walsh append error_message [sprint_list valid_values "" "" 1] 57fdb4d995SMichael Walsh if { $exit_on_fail } { 58768c1302SMichael Walsh print_error_report $error_message 59768c1302SMichael Walsh exit 1 60fdb4d995SMichael Walsh } else { 61fdb4d995SMichael Walsh error [sprint_error_report $error_message] 62fdb4d995SMichael Walsh } 63768c1302SMichael Walsh } 64768c1302SMichael Walsh 65768c1302SMichael Walsh if { $len_invalid_values == 0 } { 66768c1302SMichael Walsh # Assign default value. 67768c1302SMichael Walsh set invalid_values [list ""] 68768c1302SMichael Walsh } 69768c1302SMichael Walsh 70768c1302SMichael Walsh # Assertion: We have an invalid_values list. Processing it now. 71768c1302SMichael Walsh if { [lsearch -exact $invalid_values "${var_value}"] == -1 } { return } 72768c1302SMichael Walsh 73768c1302SMichael Walsh if { [lsearch -exact $valid_values "${var_value}"] != -1 } { return } 74768c1302SMichael Walsh append error_message "The following variable has an invalid value:\n" 75768c1302SMichael Walsh append error_message [sprint_varx $var_name $var_value "" "" 1] 76fdb4d995SMichael Walsh append error_message "\nIt must NOT be any of the following values:\n" 77768c1302SMichael Walsh append error_message [sprint_list invalid_values "" "" 1] 78fdb4d995SMichael Walsh if { $exit_on_fail } { 79fdb4d995SMichael Walsh print_error_report $error_message 80fdb4d995SMichael Walsh exit 1 81fdb4d995SMichael Walsh } else { 82fdb4d995SMichael Walsh error [sprint_error_report $error_message] 83fdb4d995SMichael Walsh } 84fdb4d995SMichael Walsh 85fdb4d995SMichael Walsh} 86fdb4d995SMichael Walsh 87fdb4d995SMichael Walsh 88fdb4d995SMichael Walshproc valid_list { var_name args } { 89fdb4d995SMichael Walsh 90*410b1787SMichael Walsh # If the value of the list variable named in var_name is not valid, print an error message and exit the 91*410b1787SMichael Walsh # program with a non-zero return code. 92fdb4d995SMichael Walsh 93fdb4d995SMichael Walsh # Description of arguments: 94*410b1787SMichael Walsh # var_name The name of the variable whose value is to be validated. This variable 95*410b1787SMichael Walsh # should be a list. For each list alement, a call to valid_value will be 96*410b1787SMichael Walsh # done. 97*410b1787SMichael Walsh # args args will be passed directly to valid_value. Please see valid_value for 98fdb4d995SMichael Walsh # details. 99fdb4d995SMichael Walsh 100fdb4d995SMichael Walsh # Example call: 101fdb4d995SMichael Walsh 102fdb4d995SMichael Walsh # set valid_procs [list "one" "two" "three"] 103fdb4d995SMichael Walsh # set proc_names [list "zero" "one" "two" "three" "four"] 104fdb4d995SMichael Walsh # valid_list proc_names {} ${valid_procs} 105fdb4d995SMichael Walsh 106fdb4d995SMichael Walsh # In this example, this procedure will fail with the following message: 107fdb4d995SMichael Walsh 108*410b1787SMichael Walsh ##(CDT) 2018/03/27 12:26:49.904870 - **ERROR** The following list has one or more invalid values (marked 109*410b1787SMichael Walsh # #with "*"): 110fdb4d995SMichael Walsh # 111fdb4d995SMichael Walsh # proc_names: 112fdb4d995SMichael Walsh # proc_names[0]: zero* 113fdb4d995SMichael Walsh # proc_names[1]: one 114fdb4d995SMichael Walsh # proc_names[2]: two 115fdb4d995SMichael Walsh # proc_names[3]: three 116fdb4d995SMichael Walsh # proc_names[4]: four* 117fdb4d995SMichael Walsh # 118fdb4d995SMichael Walsh # It must be one of the following values: 119fdb4d995SMichael Walsh # 120fdb4d995SMichael Walsh # valid_values: 121fdb4d995SMichael Walsh # valid_values[0]: one 122fdb4d995SMichael Walsh # valid_values[1]: two 123fdb4d995SMichael Walsh # valid_values[2]: three 124fdb4d995SMichael Walsh 125*410b1787SMichael Walsh # Call get_stack_var_level to relieve the caller of the need for declaring the variable as global. 126fdb4d995SMichael Walsh set stack_level [get_stack_var_level $var_name] 127fdb4d995SMichael Walsh # Access the variable value. 128fdb4d995SMichael Walsh upvar $stack_level $var_name var_value 129fdb4d995SMichael Walsh 130fdb4d995SMichael Walsh set ix 0 131fdb4d995SMichael Walsh # Create a list of index values which point to invalid list elements. 132fdb4d995SMichael Walsh set invalid_ix_list [list] 133fdb4d995SMichael Walsh foreach list_entry $var_value { 134fdb4d995SMichael Walsh incr ix 135fdb4d995SMichael Walsh if { [catch {valid_value list_entry {*}$args} result] } { 136fdb4d995SMichael Walsh lappend invalid_ix_list ${ix} 137fdb4d995SMichael Walsh } 138fdb4d995SMichael Walsh } 139fdb4d995SMichael Walsh 140fdb4d995SMichael Walsh # No errors found so return. 141fdb4d995SMichael Walsh if { [llength $invalid_ix_list] == 0 } { return } 142fdb4d995SMichael Walsh 143*410b1787SMichael Walsh # We want to do a print_list on the caller's list but we want to put an asterisk by each invalid entry 144*410b1787SMichael Walsh # (see example in prolog). 145fdb4d995SMichael Walsh 146*410b1787SMichael Walsh # Make the caller's variable name, contained in $var_name, directly accessible to this procedure. 147fdb4d995SMichael Walsh upvar $stack_level $var_name $var_name 148fdb4d995SMichael Walsh # print_list the caller's list to a string. 149fdb4d995SMichael Walsh set printed_var [sprint_list $var_name "" "" 1] 150*410b1787SMichael Walsh # Now convert the caller's printed var string to a list for easy manipulation. 151fdb4d995SMichael Walsh set printed_var_list [split $printed_var "\n"] 152fdb4d995SMichael Walsh 153*410b1787SMichael Walsh # Loop through the erroneous index list and mark corresponding entries in printed_var_list with asterisks. 154fdb4d995SMichael Walsh foreach ix $invalid_ix_list { 155fdb4d995SMichael Walsh set new_value "[lindex $printed_var_list $ix]*" 156fdb4d995SMichael Walsh set printed_var_list [lreplace $printed_var_list ${ix} ${ix} $new_value] 157fdb4d995SMichael Walsh } 158fdb4d995SMichael Walsh 159fdb4d995SMichael Walsh # Convert the printed var list back to a string. 160fdb4d995SMichael Walsh set printed_var [join $printed_var_list "\n"] 161fdb4d995SMichael Walsh append error_message "The following list has one or more invalid values" 162fdb4d995SMichael Walsh append error_message " (marked with \"*\"):\n\n" 163fdb4d995SMichael Walsh append error_message $printed_var 164*410b1787SMichael Walsh # Determine whether the caller passed invalid_values or valid_values in order to create appropriate error 165*410b1787SMichael Walsh # message. 166fdb4d995SMichael Walsh if { [lindex $args 0] != "" } { 167fdb4d995SMichael Walsh append error_message "\nIt must NOT be any of the following values:\n\n" 168fdb4d995SMichael Walsh set invalid_values [lindex $args 0] 169fdb4d995SMichael Walsh append error_message [sprint_list invalid_values "" "" 1] 170fdb4d995SMichael Walsh } else { 171fdb4d995SMichael Walsh append error_message "\nIt must be one of the following values:\n\n" 172fdb4d995SMichael Walsh set valid_values [lindex $args 1] 173fdb4d995SMichael Walsh append error_message [sprint_list valid_values "" "" 1] 174fdb4d995SMichael Walsh } 175768c1302SMichael Walsh print_error_report $error_message 176768c1302SMichael Walsh exit 1 177768c1302SMichael Walsh 178768c1302SMichael Walsh} 179768c1302SMichael Walsh 180768c1302SMichael Walsh 181768c1302SMichael Walshproc valid_integer { var_name } { 182768c1302SMichael Walsh 183*410b1787SMichael Walsh # If the value of the variable named in var_name is not a valid integer, print an error message and exit 184*410b1787SMichael Walsh # the program with a non-zero return code. 185768c1302SMichael Walsh 186768c1302SMichael Walsh # Description of arguments: 187*410b1787SMichael Walsh # var_name The name of the variable whose value is to be validated. 188768c1302SMichael Walsh 189*410b1787SMichael Walsh # Call get_stack_var_level to relieve the caller of the need for declaring the variable as global. 190768c1302SMichael Walsh set stack_level [get_stack_var_level $var_name] 191768c1302SMichael Walsh # Access the variable value. 192768c1302SMichael Walsh upvar $stack_level $var_name var_value 193768c1302SMichael Walsh 194768c1302SMichael Walsh if { [catch {format "0x%08x" "$var_value"} result] } { 195768c1302SMichael Walsh append error_message "Invalid integer value:\n" 196768c1302SMichael Walsh append error_message [sprint_varx $var_name $var_value] 197768c1302SMichael Walsh print_error_report $error_message 198768c1302SMichael Walsh exit 1 199768c1302SMichael Walsh } 200768c1302SMichael Walsh 201768c1302SMichael Walsh} 202768c1302SMichael Walsh 203768c1302SMichael Walsh 204768c1302SMichael Walshproc valid_dir_path { var_name { add_slash 1 } } { 205768c1302SMichael Walsh 206*410b1787SMichael Walsh # If the value of the variable named in var_name is not a valid directory path, print an error message and 207*410b1787SMichael Walsh # exit the program with a non-zero return code. 208768c1302SMichael Walsh 209768c1302SMichael Walsh # Description of arguments: 210*410b1787SMichael Walsh # var_name The name of the variable whose value is to be validated. 211*410b1787SMichael Walsh # add_slash If set to 1, this procedure will add a trailing slash to the directory 212*410b1787SMichael Walsh # path value. 213768c1302SMichael Walsh 214*410b1787SMichael Walsh # Call get_stack_var_level to relieve the caller of the need for declaring the variable as global. 215768c1302SMichael Walsh set stack_level [get_stack_var_level $var_name] 216768c1302SMichael Walsh # Access the variable value. 217768c1302SMichael Walsh upvar $stack_level $var_name var_value 218768c1302SMichael Walsh 219768c1302SMichael Walsh expand_shell_string var_value 220768c1302SMichael Walsh 221768c1302SMichael Walsh if { ![file isdirectory $var_value] } { 222768c1302SMichael Walsh append error_message "The following directory does not exist:\n" 223768c1302SMichael Walsh append error_message [sprint_varx $var_name $var_value "" "" 1] 224768c1302SMichael Walsh print_error_report $error_message 225768c1302SMichael Walsh exit 1 226768c1302SMichael Walsh } 227768c1302SMichael Walsh 228768c1302SMichael Walsh if { $add_slash } { add_trailing_string var_value / } 229768c1302SMichael Walsh 230768c1302SMichael Walsh} 231768c1302SMichael Walsh 232768c1302SMichael Walsh 233768c1302SMichael Walshproc valid_file_path { var_name } { 234768c1302SMichael Walsh 235*410b1787SMichael Walsh # If the value of the variable named in var_name is not a valid file path, print an error message and exit 236*410b1787SMichael Walsh # the program with a non-zero return code. 237768c1302SMichael Walsh 238768c1302SMichael Walsh # Description of arguments: 239*410b1787SMichael Walsh # var_name The name of the variable whose value is to be validated. 240768c1302SMichael Walsh 241*410b1787SMichael Walsh # Call get_stack_var_level to relieve the caller of the need for declaring the variable as global. 242768c1302SMichael Walsh set stack_level [get_stack_var_level $var_name] 243768c1302SMichael Walsh # Access the variable value. 244768c1302SMichael Walsh upvar $stack_level $var_name var_value 245768c1302SMichael Walsh 246768c1302SMichael Walsh expand_shell_string var_value 247768c1302SMichael Walsh 248768c1302SMichael Walsh if { ![file isfile $var_value] } { 249768c1302SMichael Walsh append error_message "The following file does not exist:\n" 250768c1302SMichael Walsh append error_message [sprint_varx $var_name $var_value "" "" 1] 251768c1302SMichael Walsh print_error_report $error_message 252768c1302SMichael Walsh exit 1 253768c1302SMichael Walsh } 254768c1302SMichael Walsh 255768c1302SMichael Walsh} 25605296fb6SMichael Walsh 25705296fb6SMichael Walsh 25805296fb6SMichael Walshproc get_password { {password_var_name password} } { 25905296fb6SMichael Walsh 26005296fb6SMichael Walsh # Prompt user for password and return result. 26105296fb6SMichael Walsh 262*410b1787SMichael Walsh # On error, print to stderr and terminate the program with non-zero return code. 26305296fb6SMichael Walsh 26405296fb6SMichael Walsh set prompt\ 26505296fb6SMichael Walsh [string trimright [sprint_varx "Please enter $password_var_name" ""] "\n"] 26605296fb6SMichael Walsh puts -nonewline $prompt 26705296fb6SMichael Walsh flush stdout 26805296fb6SMichael Walsh stty -echo 26905296fb6SMichael Walsh gets stdin password1 27005296fb6SMichael Walsh stty echo 27105296fb6SMichael Walsh puts "" 27205296fb6SMichael Walsh 27305296fb6SMichael Walsh set prompt [string\ 27405296fb6SMichael Walsh trimright [sprint_varx "Please re-enter $password_var_name" ""] "\n"] 27505296fb6SMichael Walsh puts -nonewline $prompt 27605296fb6SMichael Walsh flush stdout 27705296fb6SMichael Walsh stty -echo 27805296fb6SMichael Walsh gets stdin password2 27905296fb6SMichael Walsh stty echo 28005296fb6SMichael Walsh puts "" 28105296fb6SMichael Walsh 28205296fb6SMichael Walsh if { $password1 != $password2 } { 28305296fb6SMichael Walsh print_error_report "Passwords do not match.\n" 28405296fb6SMichael Walsh gen_exit_proc 1 28505296fb6SMichael Walsh } 28605296fb6SMichael Walsh 28705296fb6SMichael Walsh if { $password1 == "" } { 28805296fb6SMichael Walsh print_error_report "Need a non-blank value for $password_var_name.\n" 28905296fb6SMichael Walsh gen_exit_proc 1 29005296fb6SMichael Walsh } 29105296fb6SMichael Walsh 29205296fb6SMichael Walsh return $password1 29305296fb6SMichael Walsh 29405296fb6SMichael Walsh} 29505296fb6SMichael Walsh 29605296fb6SMichael Walsh 29705296fb6SMichael Walshproc valid_password { var_name { prompt_user 1 } } { 29805296fb6SMichael Walsh 299*410b1787SMichael Walsh # If the value of the variable named in var_name is not a valid password, print an error message and exit 300*410b1787SMichael Walsh # the program with a non-zero return code. 30105296fb6SMichael Walsh 30205296fb6SMichael Walsh # Description of arguments: 303*410b1787SMichael Walsh # var_name The name of the variable whose value is to be validated. 304*410b1787SMichael Walsh # prompt_user If the variable has a blank value, prompt the user for a value. 30505296fb6SMichael Walsh 306*410b1787SMichael Walsh # Call get_stack_var_level to relieve the caller of the need for declaring the variable as global. 30705296fb6SMichael Walsh set stack_level [get_stack_var_level $var_name] 30805296fb6SMichael Walsh # Access the variable value. 30905296fb6SMichael Walsh upvar $stack_level $var_name var_value 31005296fb6SMichael Walsh 31105296fb6SMichael Walsh if { $var_value == "" && $prompt_user } { 31205296fb6SMichael Walsh global $var_name 31305296fb6SMichael Walsh set $var_name [get_password $var_name] 31405296fb6SMichael Walsh } 31505296fb6SMichael Walsh 31605296fb6SMichael Walsh if { $var_value == "" } { 31705296fb6SMichael Walsh print_error_report "Need a non-blank value for $var_name.\n" 31805296fb6SMichael Walsh gen_exit_proc 1 31905296fb6SMichael Walsh } 32005296fb6SMichael Walsh 32105296fb6SMichael Walsh} 32205296fb6SMichael Walsh 32305296fb6SMichael Walsh 32405296fb6SMichael Walshproc process_pw_file_path {pw_file_path_var_name} { 32505296fb6SMichael Walsh 326*410b1787SMichael Walsh # Process a password file path parameter by setting or validating the corresponding password variable. 32705296fb6SMichael Walsh 328*410b1787SMichael Walsh # For example, let's say you have an os_pw_file_path parm defined. This procedure will set the global 329*410b1787SMichael Walsh # os_password variable. 33005296fb6SMichael Walsh 331*410b1787SMichael Walsh # If there is no os_password program parm defined, then the pw_file_path must exist and will be validated 332*410b1787SMichael Walsh # by this procedure. If there is an os_password program parm defined, then either the os_pw_file_path must 333*410b1787SMichael Walsh # be valid or the os_password must be valid. Again, this procedure will verify all of this. 33405296fb6SMichael Walsh 335*410b1787SMichael Walsh # When a valid pw_file_path exists, this program will read the password from it and set the global 336*410b1787SMichael Walsh # password variable with the value. 337*410b1787SMichael Walsh # Finally, this procedure will call valid_password which will prompt user if password has not been 338*410b1787SMichael Walsh # obtained by this point. 33905296fb6SMichael Walsh 34005296fb6SMichael Walsh # Description of argument(s): 341*410b1787SMichael Walsh # pw_file_path_var_name The name of a global variable that contains a file path which in turn 342*410b1787SMichael Walsh # contains a password value. The variable name must end in "pw_file_path" 343*410b1787SMichael Walsh # (e.g. "os_pw_file_path"). 34405296fb6SMichael Walsh 34505296fb6SMichael Walsh # Verify that $pw_file_path_var_name ends with "pw_file_path". 34605296fb6SMichael Walsh if { ! [regexp -expanded "pw_file_path$" $pw_file_path_var_name] } { 34705296fb6SMichael Walsh append message "Programming error - Proc [get_stack_proc_name] its" 34805296fb6SMichael Walsh append message " pw_file_path_var_name parameter to contain a value that" 34905296fb6SMichael Walsh append message "ends in \"pw_file_path\" instead of the current value:\n" 35005296fb6SMichael Walsh append message [sprint_var pw_file_path_var_name] 35105296fb6SMichael Walsh print_error $message 35205296fb6SMichael Walsh gen_exit_proc 1 35305296fb6SMichael Walsh } 35405296fb6SMichael Walsh 35505296fb6SMichael Walsh global $pw_file_path_var_name 35605296fb6SMichael Walsh expand_shell_string $pw_file_path_var_name 35705296fb6SMichael Walsh 358*410b1787SMichael Walsh # Get the prefix portion of pw_file_path_var_name which is obtained by stripping "pw_file_path" from the 359*410b1787SMichael Walsh # end. 36005296fb6SMichael Walsh regsub -expanded {pw_file_path$} $pw_file_path_var_name {} var_prefix 36105296fb6SMichael Walsh 36205296fb6SMichael Walsh # Create password_var_name. 36305296fb6SMichael Walsh set password_var_name ${var_prefix}password 36405296fb6SMichael Walsh global $password_var_name 36505296fb6SMichael Walsh 36605296fb6SMichael Walsh global longoptions pos_parms 36705296fb6SMichael Walsh regsub -all ":" "${longoptions} ${pos_parms}" {} parm_names 36805296fb6SMichael Walsh if { [lsearch -exact parm_names $password_var_name] == -1 } { 369*410b1787SMichael Walsh # If no corresponding password program parm has been defined, then the pw_file_path must be valid. 37005296fb6SMichael Walsh valid_file_path $pw_file_path_var_name 37105296fb6SMichael Walsh } 37205296fb6SMichael Walsh 37305296fb6SMichael Walsh if { [file isfile [set $pw_file_path_var_name]] } { 37405296fb6SMichael Walsh # Read the entire password file into a list, filtering comments out. 37505296fb6SMichael Walsh set file_descriptor [open [set $pw_file_path_var_name] r] 37605296fb6SMichael Walsh set file_data [list_filter_comments [split [read $file_descriptor] "\n"]] 37705296fb6SMichael Walsh close $file_descriptor 37805296fb6SMichael Walsh 37905296fb6SMichael Walsh # Assign the password value to the global password variable. 38005296fb6SMichael Walsh set $password_var_name [lindex $file_data 0] 38105296fb6SMichael Walsh # Register the password to prevent printing it. 38205296fb6SMichael Walsh register_passwords [set $password_var_name] 38305296fb6SMichael Walsh } 38405296fb6SMichael Walsh 38505296fb6SMichael Walsh # Validate the password, which includes prompting the user if need be. 38605296fb6SMichael Walsh valid_password $password_var_name 38705296fb6SMichael Walsh 38805296fb6SMichael Walsh} 389