-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathusb-storage-optimized-async-service
More file actions
187 lines (175 loc) · 7.04 KB
/
usb-storage-optimized-async-service
File metadata and controls
187 lines (175 loc) · 7.04 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
#!/bin/sh
# Oneshot service script for usb-storage-optimized-async, as a fallback when udev triggers too early during the boot
# for the script to apply (before login shell), so no USB replug to make the rule work is needed
# Limit the amount of writing cache in RAM for USB storage devices, to ensure that writing speed is correct, as opposed to async
# sync is too slow & has a risk for deteorating USB storage health, so that's why this balanced solution is being done
#
# Credits:
# - Megavolt (from Manjaro forums): for making it possible through his benchmarks, testings, findings & initial udev rule + script
log_location="/tmp/usb-storage-optimized-async-service.log"
MAX_LOG_LINES="${MAX_LOG_LINES:-1000}"
_truncate_log() {
[ ! -f "${log_location}" ] && return
if [ "${MAX_LOG_LINES}" -le 0 ]; then return; fi
awk --inplace -v keep="${MAX_LOG_LINES}" '
{
buf[NR%keep] = $0
}
END {
start = (NR>keep ? NR-keep+1 : 1)
for(i=start;i<=NR;i++) print buf[i%keep]
}' "${log_location}" 2>/dev/null || return
}
_log() {
if command -v date 1>/dev/null; then
date="[$(date "+%a %b %e %H:%M:%S %Z %Y")]: "
else
date=""
fi
echo "${date}${1}"
echo "${date}${1}" >> "${log_location}"
_truncate_log
}
dependencies="
lsusb
awk
bc
"
missing_dependencies=""
missing_dep_check=0
for dep in ${dependencies}; do
if ! command -v "${dep}" 1>/dev/null; then
missing_dep_check=1
if [ -z "${missing_dependencies}" ]; then
missing_dependencies="$dep"
else
missing_dependencies="${missing_dependencies} ${dep}"
fi
fi
done
if [ "$missing_dep_check" = 1 ]; then
_log "Missing dependency/ies '${missing_dependencies}'. Cannot continue running the program."
_log "Please install '${missing_dependencies}' dependency/ies and try again."
_log "Exiting..."
exit 1
fi
_cat() {
while IFS= read -r line; do
printf '%s\n' "$line"
done < "${1}"
}
# Get kernel version of the running system
if [ -r "/proc/sys/kernel/osrelease" ]; then
read -r release < "/proc/sys/kernel/osrelease"
kernel_ver="$release"
else
kernel_ver="undetected"
fi
get_usb_storage_block() {
# POSIX basename
_basename() {
dir=${1%${1##*[!/]}}
dir=${dir##*/}
echo "${dir:-/}"
}
_contains_id() {
while IFS= read -r line || [ -n "$line" ]; do
case "$line" in
*"$1"*) return 0 ;;
esac
done < "$2"
return 1
}
usb_vendor_id="${1%:*}"
usb_product_id="${1#*:}"
usb_dirs=""
for dev in /sys/bus/usb/devices/*; do
[ -e "$dev/idVendor" ] || continue
real_path=$(cd -P "$dev" && echo "$PWD")
usb_dirs="$usb_dirs$real_path "
done
for path in $usb_dirs; do
if _contains_id "$usb_vendor_id" "$path/idVendor" && \
_contains_id "$usb_product_id" "$path/idProduct"; then
block_dev=$(
for d in /sys/block/*; do
[ -e "$d" ] || continue
target=$(cd -P "$d" && echo "$PWD")
case "$target" in
"$path"*)
suffix="${target#$path/}"
case "$suffix" in
/*) ;;
*) echo "$suffix"; break ;;
esac
;;
esac
done
)
if [ -n "$block_dev" ]; then
_basename "$block_dev"
fi
fi
done
}
USB_VENDOR_PRODUCT="$(lsusb -v -t | awk '/Driver=usb-storage/{getline; gsub(/^[ \t]+|[ \t]+$/, "", $0); print $0}' | awk '/ID/{print $2}')"
if [ -z "$USB_VENDOR_PRODUCT" ]; then
_log "No USB vendor and product detected in lsusb output"
_log "Exiting..."
exit 0
fi
for usb_vendor_product in ${USB_VENDOR_PRODUCT}; do
if [ -z $(echo "$usb_vendor_product" | awk '$0 ~ /^[0-9]+:[0-9]+$/') ]; then
_log "USB vendor and product string doesn't match the integer:integer format"
continue
fi
usb_vendor="${usb_vendor_product%:*}"
usb_product="${usb_vendor_product#*:}"
block_device="$(get_usb_storage_block "$usb_vendor:$usb_product")"
if [ -z "$block_device" ]; then
_log "Block device string is empty, can't get the path of this USB storage device, skipping this USB storage device with ID '${usb_vendor}:${usb_product}'"
continue
fi
if [ ! -e "/sys/block/${block_device}/bdi/max_bytes" ]; then
_log "Your kernel version '${kernel_ver}' doesn't support the 'max_bytes' BDI value. At least Linux 6.1 kernel is required."
_log "Exiting..."
exit 1
fi
current_usb_speed=$(lsusb -t -v | awk -v vendor_id="${usb_vendor}" -v product_id="${usb_product}" '$0 ~ vendor_id ":" product_id {split(prev, a, " "); print a[length(a)]} {prev=$0}' | awk '{gsub(/[a-zA-Z]/, ""); print}')
if [ -z "${current_usb_speed}" ]; then
_log "Current USB speed for this USB storage device is empty, skiping this USB storage device with ID '${usb_vendor}:${usb_product}', path '/sys/block/${block_device}'"
continue
fi
if [ $(echo "${current_usb_speed}" | awk 'END {print NR}') -gt 1 ]; then
_log "There are multiple strings of current USB speed for this USB storage device, skiping this USB storage device with ID '${usb_vendor}:${usb_product}', path '/sys/block/${block_device}'"
continue
fi
if ! echo "${current_usb_speed}" | awk '$0 ~ /^-?[0-9]+(\.[0-9]+)?$/ {found=1} END {exit !found}'; then
_log "Current USB speed for this USB storage device is not in integer or float values, skiping this USB storage device with ID '${usb_vendor}:${usb_product}', path '/sys/block/${block_device}'"
continue
fi
buffer_time="0.05"
safety_factor="1.3"
max_bytes_unrounded=$(echo "((${current_usb_speed} / 8) * ${buffer_time} * ${safety_factor}) * 1024 * 1024" | bc)
max_bytes=$(echo "(${max_bytes_unrounded}+0.5)/1" | bc)
# General max_bytes ideal value results (thanks to MegaVolt from Manjaro forums):
# 12.5MB/s: 62915
# 100MB/s: 817889
# 500MB/s: 4225761
# 1000MB/s: 8514437
# 5000MB/s: 42593157
# 10000MB/s: 85196800
# Apply bandwidth defined value
if [ -e "/sys/block/${block_device}" ]; then
echo "1" > "/sys/block/${block_device}/bdi/strict_limit"
echo "${max_bytes}" > "/sys/block/${block_device}/bdi/max_bytes"
_log "Applied the usb-storage-optimized-async fix for USB storage device with ID '${usb_vendor}:${usb_product}', path '/sys/block/${block_device}'"
_log "Calculated 'max_bytes' value: ${max_bytes}"
_log "Note that the applied 'max_bytes' value won't match exactly to the calculated value, but it will be rounded up to be close"
_log "/sys/block/${block_device}/bdi/strict_limit = $(_cat /sys/block/${block_device}/bdi/strict_limit)"
_log "/sys/block/${block_device}/bdi/max_bytes = $(_cat /sys/block/${block_device}/bdi/max_bytes)"
else
_log "This block device '${block_device}' doesn't exist, skiping this USB storage device with ID '${usb_vendor}:${usb_product}', non-existent path '/sys/block/${block_device}'"
continue
fi
done