From c522f4d2a6c69821a87818f7fb28c8fbef8326b2 Mon Sep 17 00:00:00 2001 From: Harald Hope Date: Thu, 7 Sep 2017 10:05:45 -0700 Subject: [PATCH] New version, tarball, man page. This closes issue #122. Adds support for including nvme disk capacity in full disk capacity listing. Adds nvme name/serial/firmware revision number. The latter is a new -Dxx output option. Note that as far as I could tell, so far, nvme is the only disk type that has firmware revision data. Added support for nvme disk temperature as well, that requires the cli tool nvme. Updated AMD microarchitecture list to be more granular and complete. Added Intel microarch type. Note that they are releasing a few new microarchitectures soon but I was not able to find any model numbers for those. --- inxi | 219 +++++++++++++++++++++++++++++++++++-------------- inxi.1 | 5 +- inxi.changelog | 21 +++++ 3 files changed, 183 insertions(+), 62 deletions(-) diff --git a/inxi b/inxi index 92a7bfe..c76b7f9 100755 --- a/inxi +++ b/inxi @@ -2,8 +2,8 @@ ######################################################################## SELF_NAME='inxi' # don't quote the following, parsers grab these too -SELF_VERSION=2.3.37 -SELF_DATE=2017-08-23 +SELF_VERSION=2.3.38 +SELF_DATE=2017-09-07 SELF_PATCH=00 ######################################################################## #### SPECIAL THANKS @@ -2161,6 +2161,11 @@ debug_data_collector() cat /proc/ide/*/* &> $Debug_Data_Dir/proc-ide-hdx-cat.txt cat /etc/fstab &> $Debug_Data_Dir/etc-fstab.txt cat /etc/mtab &> $Debug_Data_Dir/etc-mtab.txt + if type -p nvme &>/dev/null; then + touch $Debug_Data_Dir/nvme-present + else + touch $Debug_Data_Dir/nvme-absent + fi fi if [[ $1 == 'disk' || $1 == 'sys' || $1 == 'all' ]];then echo 'Collecting networking data...' @@ -2175,9 +2180,16 @@ debug_data_collector() if [[ -z $BSD_TYPE ]] && [[ $1 == 'disk' || $1 == 'sys' || $1 == 'all' ]];then echo $Line sys_data_file=$SELF_DATA_DIR/$Debug_Data_Dir/xiin-sys.txt + echo "Getting file paths in /sys..." + ls_sys 1 + ls_sys 2 + ls_sys 3 + ls_sys 4 + # note, this generates more lines than the full sys parsing, so only use if requird + # ls_sys 5 touch $sys_data_file if type -p perl &>/dev/null;then - echo "Collecting data from /sys..." + echo "Parsing /sys files..." echo -n "Using Perl: " && perl --version | grep -oE 'v[0-9.]+' sys_traverse_data="$( perl -e ' use File::Find; @@ -2330,6 +2342,38 @@ debug_data_collector() fi exit 0 } +## args: $1 - level +ls_sys() +{ + local files='' + case $1 in + 1)files='/sys/';; + 2)files='/sys/*/';; + 3)files='/sys/*/*/';; + 4)files='/sys/*/*/*/';; # this should be enough for most use cases + 5)files='/sys/*/*/*/*/';; # very large file, shows best shortcuts though + 6)files='/sys/*/*/*/*/*/';; # slows down too much, too big, can cause ls error + 7)files='/sys/*/*/*/*/*/*/';; # impossibly big, will fail + esac + ls -l $files | awk '{ + if (NF > 7) { + if ($1 ~/^d/){ + f="d - " + } + else if ($1 ~/^l/){ + f="l - " + } + else { + f="f - " + } + # includes -> target for symbolic link if present + print "\t" f $9 " " $10 " " $11 + } + else if (!/^total / ) { + print $0 + } + }' &> $Debug_Data_Dir/sys-level-$1.txt +} ## args: $1 - debugger file name upload_debugger_data() @@ -3352,7 +3396,7 @@ show_options() print_lines_basic "2" "-A" "Chip vendor:product ID for each audio device." print_lines_basic "2" "-B" "serial number, voltage (if available)." print_lines_basic "2" "-C" "Minimum CPU speed, if available." - print_lines_basic "2" "-D" "Disk serial number." + print_lines_basic "2" "-D" "Disk serial number; Firmware rev. if available." print_lines_basic "2" "-G" "Chip vendor:product ID for each video card; (mir/wayland only) compositor (alpha test); OpenGL compatibility version, if free drivers and available." print_lines_basic "2" "-I" "Other detected installed gcc versions (if present). System default runlevel. Adds parent program (or tty) for shell info if not in IRC (like Konsole or Gterm). Adds Init/RC (if found) version number." print_lines_basic "2" "-m" "Manufacturer, Serial Number, single/double bank (if found)." @@ -4577,6 +4621,7 @@ get_cpu_architecture() { eval $LOGFS case $1 in + # https://en.wikipedia.org/wiki/List_of_AMD_CPU_microarchitectures amd) case $2 in 4) @@ -4618,16 +4663,16 @@ get_cpu_architecture() ;; 11) case $3 in - 3)ARCH='K8 rev.E+';; + 3)ARCH='Turion X2 Ultra';; esac ;; - 12) + 12) # might also need cache handling like 14/16 case $3 in - 1)ARCH='K10';; - *)ARCH='K10';; + 1)ARCH='Fusion';; + *)ARCH='Fusion';; esac ;; - 14) + 14) # SOC, apu case $3 in 1|2)ARCH='Bobcat';; *)ARCH='Bobcat';; @@ -4635,17 +4680,17 @@ get_cpu_architecture() ;; 15) case $3 in - 0|1)ARCH='Bulldozer';; - 2|10|13)ARCH='Piledriver';; - 30|38)ARCH='Steamroller';; - 60|65|70)ARCH='Excavator';; + 0|1|2|3|4|5|6|7|8|9|A|B|C|D|E|F)ARCH='Bulldozer';; + 10|11|12|13|14|15|16|17|18|19|1A|1B|1C|1D|1E|1F)ARCH='Piledriver';; + 30|31|32|33|34|35|36|37|38|39|3A|3B|3C|3D|3E|3F)ARCH='Steamroller';; + 60|61|62|63|64|65|66|67|68|69|6A|6B|6C|6D|6E|6F|70|71|72|73|74|75|76|77|78|79|7A|7B|7C|7D|7E|7F)ARCH='Excavator';; *)ARCH='Bulldozer';; esac ;; - 16) + 16) # SOC, apu case $3 in - 0)ARCH='Jaguar';; - 30)ARCH='Puma';; + 0|1|2|3|4|5|6|7|8|9|A|B|C|D|E|F)ARCH='Jaguar';; + 30|31|32|33|34|35|36|37|38|39|3A|3B|3C|3D|3E|3F)ARCH='Puma';; *)ARCH='Jaguar';; esac ;; @@ -4720,10 +4765,13 @@ get_cpu_architecture() 3C|3F|45|46)ARCH='Haswell';; 3D|47|4F|56)ARCH='Broadwell';; 4E|55|9E)ARCH='Skylake';; + 5E)ARCH='Skylake-S';; 4C|5D)ARCH='Airmont';; 8E|9E)ARCH='Kaby Lake';; 57)ARCH='Knights Landing';; 85)ARCH='Knights Mill';; + # product codes: https://en.wikipedia.org/wiki/List_of_Intel_microprocessors + # coming: coffee lake; cannonlake; icelake; tigerlake esac ;; B) @@ -7394,12 +7442,13 @@ get_hdd_data_basic() printf( $NF",%.1fGB,,\n", driveSize ) } # See http://lanana.org/docs/device-list/devices-2.6+.txt for major numbers used below + # See https://www.mjmwired.net/kernel/Documentation/devices.txt for kernel 4.x device numbers # $1 ~ /^(3|22|33|8)$/ && $2 % 16 == 0 { # size += $3 # } # special case from this data: 8 0 156290904 sda - # note: vm has 252/253/254 known starter, grsec has 202 - $1 ~ /^(3|8|22|33|202|252|253|254)$/ && $NF ~ /[hsv]d[a-z]+$/ && ( $2 % 16 == 0 || $2 % 16 == 8 ) { + # note: known starters: vm: 252/253/254; grsec: 202; nvme: 259 + $1 ~ /^(3|8|22|33|202|252|253|254|259)$/ && $NF ~ /(nvme[0-9]+n[0-9]+|[hsv]d[a-z]+)$/ && ( $2 % 16 == 0 || $2 % 16 == 8 ) { size += $3 } END { @@ -7446,21 +7495,22 @@ get_hard_drive_data_advanced() local a_temp_working='' a_temp_scsi='' temp_holder='' temp_name='' i='' j='' local sd_ls_by_id='' ls_disk_by_id='' ls_disk_by_path='' usb_exists='' a_temp='' local firewire_exists='' thunderbolt_exists='' thunderbolt_exists='' hdd_temp hdd_serial='' + local firmware_rev='' working_path='' block_type='' ## check for all ide type drives, non libata, only do it if hdx is in array ## this is now being updated for new /sys type paths, this may handle that ok too - if [[ -n $( grep -E 'hd[a-z]' <<< ${A_HDD_DATA[@]} ) ]];then + if [[ -n $( grep -Es 'hd[a-z]' <<< ${A_HDD_DATA[@]} ) ]];then # remember, we're using the last array item to store the total size of disks for (( i=0; i < ${#A_HDD_DATA[@]} - 1; i++ )) do IFS="," a_temp_working=( ${A_HDD_DATA[i]} ) IFS="$ORIGINAL_IFS" - if [[ -n $( grep -E '^hd[a-z]' <<< ${a_temp_working[0]} ) ]];then + if [[ -z ${a_temp_working[0]/*hd[a-z]*/} ]];then if [[ -e /proc/ide/${a_temp_working[0]}/model ]];then a_temp_working[2]="$( remove_erroneous_chars /proc/ide/${a_temp_working[0]}/model )" else - a_temp_working[2]="Name n/a" + a_temp_working[2]='' fi # these loops are to easily extend the cpu array created in the gawk script above with more fields per cpu. for (( j=0; j < ${#a_temp_working[@]}; j++ )) @@ -7509,23 +7559,42 @@ get_hard_drive_data_advanced() log_function_data 'cat' "$FILE_SCSI" fi IFS="$ORIGINAL_IFS" - ## then we'll loop through that array looking for matches. - if [[ -n $( grep -E 'sd[a-z]' <<< ${A_HDD_DATA[@]} ) ]];then + if [[ -n $( grep -Es 'sd[a-z]|nvme' <<< ${A_HDD_DATA[@]} ) ]];then # first pack the main ls variable so we don't have to keep using ls /dev... # not all systems have /dev/disk/by-id ls_disk_by_id="$( ls -l /dev/disk/by-id 2>/dev/null )" ls_disk_by_path="$( ls -l /dev/disk/by-path 2>/dev/null )" for (( i=0; i < ${#A_HDD_DATA[@]} - 1; i++ )) do - if [[ -n $( grep -E '^sd[a-z]' <<< ${A_HDD_DATA[$i]} ) ]];then + firmware_rev='' + hdd_temp='' + hdd_serial='' + temp_name='' + working_path='' + block_type='' + if [[ -z ${A_HDD_DATA[$i]/*nvme*/} ]];then + block_type='nvme' + elif [[ -z ${A_HDD_DATA[$i]/*sd[a-z]*/} ]];then + block_type='sdx' + fi + if [[ -n $block_type ]];then IFS="," a_temp_working=( ${A_HDD_DATA[$i]} ) IFS="$ORIGINAL_IFS" + if [[ $block_type == 'sdx' ]];then + working_path=/sys/block/${a_temp_working[0]}/device/ + elif [[ $block_type == 'nvme' ]];then + # this results in: + # /sys/devices/pci0000:00/0000:00:03.2/0000:06:00.0/nvme/nvme0/nvme0n1 + # but we want to go one level down so slice off trailing nvme0n1 + working_path=$(readlink -f /sys/block/${a_temp_working[0]} 2>/dev/null ) + working_path=${working_path%nvme*} + fi # /sys/block/[sda,hda]/device/model # this is handles the new /sys data types first - if [[ -e /sys/block/${a_temp_working[0]}/device/model ]];then - temp_name="$( remove_erroneous_chars /sys/block/${a_temp_working[0]}/device/model )" + if [[ -e ${working_path}model ]];then + temp_name="$( remove_erroneous_chars ${working_path}model )" temp_name=$( cut -d '-' -f 1 <<< ${temp_name// /_} ) elif [[ ${#a_temp_scsi[@]} -gt 0 ]];then for (( j=0; j < ${#a_temp_scsi[@]}; j++ )) @@ -7546,15 +7615,14 @@ get_hard_drive_data_advanced() fi done fi - - if [[ -z $temp_name ]];then - temp_name="Name n/a" - # maybe remove this from the conditional, detection of usb may not depend on the name - else # + # I don't know identifier for thunderbolt in /dev/disk/by-id / /dev/disk/by-path + if [[ -n $temp_name && -n "$ls_disk_by_id" ]];then usb_exists=$( grep -Em1 "usb-.*$temp_name.*${a_temp_working[0]}$" <<< "$ls_disk_by_id" ) firewire_exists=$( grep -Em1 "ieee1394-.*$temp_name.*${a_temp_working[0]}$" <<< "$ls_disk_by_id" ) # thunderbolt_exists=$( grep -Em1 "ieee1394-.*$temp_name.*${a_temp_working[0]}$" <<< "$ls_disk_by_id" ) # note: sometimes with wwn- numbering usb does not appear in by-id but it does in by-path + fi + if [[ -n "$ls_disk_by_path" ]];then if [[ -z $usb_exists ]];then usb_exists=$( grep -Em1 "usb-.*${a_temp_working[0]}$" <<< "$ls_disk_by_path" ) fi @@ -7569,7 +7637,6 @@ get_hard_drive_data_advanced() fi fi a_temp_working[2]=$temp_name - # these loops are to easily extend the cpu array created in the gawk script above with more fields per cpu. for (( j=0; j < ${#a_temp_working[@]}; j++ )) do if [[ $j -gt 0 ]];then @@ -7584,15 +7651,21 @@ get_hard_drive_data_advanced() a_temp_working=( ${A_HDD_DATA[i]} ) # echo "a:" ${a_temp_working[@]} IFS="$ORIGINAL_IFS" - hdd_temp='' - hdd_serial='' + if [[ -n ${a_temp_working[1]} ]];then hdd_temp=$( get_hdd_temp_data "/dev/${a_temp_working[0]}" ) fi if [[ $B_EXTRA_EXTRA_DATA == 'true' ]];then - hdd_serial=$( get_hdd_serial_number "${a_temp_working[0]}" ) + if [[ -e ${working_path}serial ]];then + hdd_serial="$( remove_erroneous_chars ${working_path}serial )" + else + hdd_serial=$( get_hdd_serial_number "${a_temp_working[0]}" ) + fi + if [[ -e ${working_path}firmware_rev ]];then + firmware_rev="$( remove_erroneous_chars ${working_path}firmware_rev )" + fi fi - A_HDD_DATA[i]="${a_temp_working[0]},${a_temp_working[1]},${a_temp_working[2]},${a_temp_working[3]},$hdd_serial,$hdd_temp" + A_HDD_DATA[i]="${a_temp_working[0]},${a_temp_working[1]},${a_temp_working[2]},${a_temp_working[3]},$hdd_serial,$hdd_temp,$firmware_rev" # echo b: ${A_HDD_DATA[i]} fi done @@ -7669,7 +7742,7 @@ get_hard_drive_data_bsd() if ( aDisks[aIds[key], "model"] !~ /raid/ ) { workingSize = aDisks[aIds[key], "size"]/1000 workingSize = sprintf( "%.1fGB", workingSize ) - print aDisks[aIds[key], "id"] "," workingSize "," aDisks[aIds[key], "model"] "," "," aDisks[aIds[key], "serial"] "," + print aDisks[aIds[key], "id"] "," workingSize "," aDisks[aIds[key], "model"] "," "," aDisks[aIds[key], "serial"] ",," } } size = size/1000 # calculate size in GB size @@ -7716,10 +7789,11 @@ get_hdd_serial_number() get_partition_dev_data 'id' # lrwxrwxrwx 1 root root 9 Apr 26 09:32 scsi-SATA_ST3160827AS_5MT2HMH6 -> ../../sdc + # exception: ata-InnoDisk_Corp._-_mSATA_3ME3_BCA34401050060191 -> ../../sda # exit on the first instance hdd_serial=$( gawk ' /'$1'$/ { - serial=gensub( /^(.+)_([^_]+)$/, "\\2", 1, $9 ) + serial=gensub( /(.+)_([^_]+)$/, "\\2", 1, $9 ) print serial exit }' <<< "$DEV_DISK_ID" ) @@ -7734,29 +7808,45 @@ get_hdd_serial_number() get_hdd_temp_data() { eval $LOGFS - local hdd_temp='' sudo_command='' + local hdd_temp='' sudo_command='' device=$1 - if [[ $B_HDDTEMP_TESTED != 'true' ]];then - B_HDDTEMP_TESTED='true' - HDDTEMP_PATH=$( type -p hddtemp ) - fi if [[ $B_SUDO_TESTED != 'true' ]];then B_SUDO_TESTED='true' SUDO_PATH=$( type -p sudo ) fi - - if [[ -n $HDDTEMP_PATH && -n $1 ]];then - # only use sudo if not root, -n option requires sudo -V 1.7 or greater. sudo will just error out - # which is the safest course here for now, otherwise that interactive sudo password thing is too annoying - # important: -n makes it non interactive, no prompt for password - if [[ $B_ROOT != 'true' && -n $SUDO_PATH ]];then - sudo_command='sudo -n ' + # only use sudo if not root, -n option requires sudo -V 1.7 or greater. sudo will just error out + # which is the safest course here for now, otherwise that interactive sudo password thing is too annoying + # important: -n makes it non interactive, no prompt for password + if [[ $B_ROOT != 'true' && -n $SUDO_PATH ]];then + sudo_command='sudo -n ' + fi + # try this to see if hddtemp gives result for the base name + if [[ -z ${device/*nvme*/} ]];then + if type -p nvme &>/dev/null;then + device=${device%n[0-9]} + # this will fail if regular user and no sudo present, but that's fine, it will just return null + hdd_temp=$( eval $sudo_command nvme smart-log $device 2>/dev/null | gawk -F ':' ' + BEGIN { + IGNORECASE=1 + } + # other rows may have: Temperature sensor 1 : + /^temperature\s*:/ { + gsub(/^[[:space:]]+|[[:space:]]*C$/,"",$2) + print $2 + }' ) fi - # this will fail if regular user and no sudo present, but that's fine, it will just return null - hdd_temp=$( eval $sudo_command $HDDTEMP_PATH -nq -u C $1 ) - if [[ -n $hdd_temp && -n $( grep -E '^([0-9\.]+)$' <<< $hdd_temp ) ]];then - echo $hdd_temp + else + if [[ $B_HDDTEMP_TESTED != 'true' ]];then + B_HDDTEMP_TESTED='true' + HDDTEMP_PATH=$( type -p hddtemp ) fi + if [[ -n $HDDTEMP_PATH && -n $device ]];then + # this will fail if regular user and no sudo present, but that's fine, it will just return null + hdd_temp=$( eval $sudo_command $HDDTEMP_PATH -nq -u C $device ) + fi + fi + if [[ -n $hdd_temp && -z ${hdd_temp//[0-9]/} ]];then + echo $hdd_temp fi eval $LOGFE } @@ -8654,12 +8744,11 @@ get_networking_wan_ip_data() # get ip using wget redirect to stdout. This is a clean, text only IP output url, # single line only, ending in the ip address. May have to modify this in the future # to handle ipv4 and ipv6 addresses but should not be necessary. - # awk has bad regex handling so checking it with grep -E instead # ip=$( echo 2001:0db8:85a3:0000:0000:8a2e:0370:7334 | gawk --re-interval ' # ip=$( wget -q -O - $WAN_IP_URL | gawk --re-interval ' - # this generates a direct dns based ipv4 ip address, but if opendns.com goes down, the fall + # this generates a direct dns based ipv4 ip address, but if opendns.com goes down, + # the fall backs will still work. # note: consistently slower than domain based: dig +short +time=1 +tries=1 myip.opendns.com. A @208.67.222.222 - # backs will still work. if [[ -n $DNSTOOL ]];then ip=$( dig +short +time=1 +tries=1 myip.opendns.com @resolver1.opendns.com 2>/dev/null) fi @@ -12623,7 +12712,6 @@ print_short_data() fi fi - #set_color_scheme 12 if [[ $B_IRC == 'true' ]];then for i in $C1 $C2 $CN @@ -13110,7 +13198,10 @@ print_cpu_data() # note that we need to multiply by number of actual cpus here to get true cache size if [[ -n ${a_cpu_working[2]} ]];then if [[ -z $BSD_TYPE ]];then - if [[ $cpu_vendor != 'intel' ]];then + # AMD SOS chips appear to report full L2 cache per core + if [[ "${a_cpu_info[3]}" == 'amd' ]] && [[ "${a_cpu_info[4]}" == '14' || "${a_cpu_info[4]}" == '16' ]];then + cpu_cache=$( calculate_multicore_data "${a_cpu_working[2]}" "$cpu_physical_count" ) + elif [[ $cpu_vendor != 'intel' ]];then cpu_cache=$( calculate_multicore_data "${a_cpu_working[2]}" "$(( $cpu_core_count * $cpu_physical_count ))" ) else cpu_cache=$( calculate_multicore_data "${a_cpu_working[2]}" "$cpu_physical_count" ) @@ -13663,7 +13754,7 @@ print_hard_disk_data() { eval $LOGFS local hdd_data='' hdd_data_2='' a_hdd_working='' hdd_temp_data='' hdd_string='' - local hdd_serial='' dev_string='/dev/' + local hdd_serial='' dev_string='/dev/' firmware_rev='' local dev_data='' size_data='' hdd_model='' usb_data='' hdd_name='' local Line_Starter='Drives:' # inherited by print_optical_drives # load A_HDD_DATA - this will also populate the full bsd disk data array values @@ -13728,6 +13819,12 @@ print_hard_disk_data() hdd_serial='N/A' fi hdd_serial="${C1}serial$SEP3${C2} $hdd_serial " + if [[ -n ${a_hdd_working[6]} ]];then + firmware_rev=${a_hdd_working[6]} + firmware_rev="${C1}firmware$SEP3${C2} $firmware_rev " + else + firmware_rev='' + fi fi dev_data="$dev_string${a_hdd_working[0]} " fi @@ -13740,7 +13837,7 @@ print_hard_disk_data() hdd_name="${C1}model$SEP3${C2} $hdd_name_temp" hdd_string="${C1}ID-$((i+1))$SEP3${C2} $usb_data$dev_data$hdd_name$size_data" part_1_data="$hdd_model$hdd_string " - part_2_data="$hdd_serial$hdd_temp_data" + part_2_data="$hdd_serial$hdd_temp_data$firmware_rev" ## Forcing the capacity to print on its own row, and the first drive on its own ## then each disk prints on its own line, or two lines, depending on console/output width if [[ $i -eq 0 ]];then diff --git a/inxi.1 b/inxi.1 index 48a588a..27802e4 100644 --- a/inxi.1 +++ b/inxi.1 @@ -1,4 +1,4 @@ -.TH INXI 1 "2017\-08\-23" inxi "inxi manual" +.TH INXI 1 "2017\-09\-07" inxi "inxi manual" .SH NAME inxi \- Command line system information script for console and IRC .SH SYNOPSIS @@ -465,6 +465,9 @@ Note that \fBvolts\fR shows the data (if available) as: Voltage Now / Minimum De .B \-xx \-D \- Adds disk serial number. .TP +.B \-xx \-D +\- Adds disk firmware revision number, if available (nvme and possibly other types). +.TP .B \-xx \-G \- Adds vendor:product ID of each Graphics card. .TP diff --git a/inxi.changelog b/inxi.changelog index ebad4f7..cc00b72 100644 --- a/inxi.changelog +++ b/inxi.changelog @@ -1,3 +1,24 @@ +===================================================================================== +Version: 2.3.38 +Patch Version: 00 +Script Date: 2017-09-07 +----------------------------------- +Changes: +----------------------------------- +New version, tarball, man page. This closes issue #122. Adds support for including +nvme disk capacity in full disk capacity listing. Adds nvme name/serial/firmware +revision number. The latter is a new -Dxx output option. Note that as far as I could +tell, so far, nvme is the only disk type that has firmware revision data. + +Added support for nvme disk temperature as well, that requires the cli tool nvme. + +Updated AMD microarchitecture list to be more granular and complete. Added Intel +microarch type. Note that they are releasing a few new microarchitectures soon but I +was not able to find any model numbers for those. + +----------------------------------- +-- Harald Hope - Thu, 07 Sep 2017 10:00:06 -0700 + ===================================================================================== Version: 2.3.37 Patch Version: 00