Compare commits

...

20 commits

Author SHA1 Message Date
Jérémy Lecour 297cafe04b
add guard clauses if dpkg status is missing 2024-03-29 10:31:53 +01:00
Jérémy Lecour c67c1ca7ad
rsync /etc with --delete 2023-12-06 15:20:23 +01:00
Jérémy Lecour 3426b7005e
Release 23.11 2023-11-20 18:59:42 +01:00
Jérémy Lecour a0340b36f2
add sleep to pt-mysql-summary 2023-11-20 18:47:57 +01:00
Jérémy Lecour d57c664c22
add --[no-]mysql-summary to execute pt-mysql-summary(1) if present 2023-11-20 18:41:52 +01:00
Jérémy Lecour becdca41d8
Release 23.08 2023-08-01 22:57:22 +02:00
Jérémy Lecour 15564bad2d
forgotten stderr 2023-08-01 22:55:18 +02:00
Jérémy Lecour d7b20762d8
whitespace 2023-08-01 22:48:58 +02:00
Jérémy Lecour a2976936bf
Delete empty error files at the end 2023-08-01 22:48:49 +02:00
Jérémy Lecour d29aa7887c
Store iptables/nft stderr output in special files 2023-08-01 22:47:33 +02:00
Jérémy Lecour 80d6b5a67e Release 22.04.3 2022-04-26 18:21:03 +02:00
Jérémy Lecour 14e67de360 task_mysql_processes: try mysqladmin ping before dumping the processlist 2022-04-26 18:18:56 +02:00
Jérémy Lecour 83a7d32766 debug() accepts stdin 2022-04-26 18:16:47 +02:00
Jérémy Lecour 805e23e7e3 Release 22.04.2 2022-04-26 09:55:55 +02:00
Jérémy Lecour b6ad4a0929 Ignore errors with mysqladmin, until we find a way to deal with multiple instances. 2022-04-26 09:55:31 +02:00
Jérémy Lecour 4bf41dedb3 fix stupid quote error 2022-04-20 11:14:26 +02:00
Jérémy Lecour 15b349b72a Release 22.04.1 2022-04-20 11:06:17 +02:00
Jérémy Lecour 4896c87cc8 redirect df errors to stdout 2022-04-20 11:05:37 +02:00
Jérémy Lecour 2948d6faf8 Release 22.04 2022-04-03 11:17:45 +02:00
Jérémy Lecour 3a716ee025 Better iptables/ip6tables dump 2022-04-03 11:16:37 +02:00
2 changed files with 229 additions and 88 deletions

View file

@ -14,12 +14,56 @@ The **patch** part changes is incremented if multiple releases happen the same m
### Changed ### Changed
* rsync `/etc` with `--delete`
* add guard clauses if dpkg status is missing
### Fixed ### Fixed
### Removed ### Removed
### Security ### Security
## [23.11] 2023-11-20
### Added
* add `--[no-]mysql-summary` to execute `pt-mysql-summary(1)` if present
## [23.08] 2023-08-01
### Added
* Store iptables/nft stderr output in special files
* … and delete the empty ones at the end
## [22.04.3] 2022-04-26
### Added
* debug() accepts stdin
### Changed
* task_mysql_processes: try mysqladmin ping before dumping the processlist
## [22.04.2] 2022-04-26
### Changed
* Ignore errors with mysqladmin, until we find a way to deal with multiple instances.
## [22.04.1] 2022-04-20
### Changed
redirect df errors to stdout
## [22.04] 2022-04-03
### Changed
Better iptables/ip6tables dump
## [22.03.10] 2022-03-29 ## [22.03.10] 2022-03-29
### Changed ### Changed
@ -38,4 +82,4 @@ use nft is available and ignore iptables errors
## [22.03.8] 2022-03-27 ## [22.03.8] 2022-03-27
dump-server-state has its own repository and changelog dump-server-state has its own repository and changelog

View file

@ -3,7 +3,7 @@
PROGNAME="dump-server-state" PROGNAME="dump-server-state"
REPOSITORY="https://gitea.evolix.org/evolix/dump-server-state" REPOSITORY="https://gitea.evolix.org/evolix/dump-server-state"
VERSION="22.03.10" VERSION="23.11"
readonly VERSION readonly VERSION
dump_dir= dump_dir=
@ -15,7 +15,7 @@ show_version() {
cat <<END cat <<END
${PROGNAME} version ${VERSION} ${PROGNAME} version ${VERSION}
Copyright 2018-2022 Evolix <info@evolix.fr>, Copyright 2018-2023 Evolix <info@evolix.fr>,
Jérémy Lecour <jlecour@evolix.fr>, Jérémy Lecour <jlecour@evolix.fr>,
Éric Morino <emorino@evolix.fr>, Éric Morino <emorino@evolix.fr>,
Brice Waegeneire <bwaegeneire@evolix.fr> Brice Waegeneire <bwaegeneire@evolix.fr>
@ -23,7 +23,7 @@ Copyright 2018-2022 Evolix <info@evolix.fr>,
${REPOSITORY} ${REPOSITORY}
${PROGNAME} comes with ABSOLUTELY NO WARRANTY.This is free software, ${PROGNAME} comes with ABSOLUTELY NO WARRANTY. This is free software,
and you are welcome to redistribute it under certain conditions. and you are welcome to redistribute it under certain conditions.
See the GNU General Public License v3.0 for details. See the GNU General Public License v3.0 for details.
END END
@ -35,48 +35,50 @@ ${PROGNAME} is dumping information related to the state of the server.
Usage: ${PROGNAME} --dump-dir=/path/to/dump/directory [OPTIONS] Usage: ${PROGNAME} --dump-dir=/path/to/dump/directory [OPTIONS]
Main options Main options
-d, --dump-dir path to the directory where data will be stored -d, --dump-dir path to the directory where data will be stored
--backup-dir legacy option for dump directory --backup-dir legacy option for dump directory
-f, --force keep existing dump directory and its content -f, --force keep existing dump directory and its content
-v, --verbose print details about each task -v, --verbose print details about each task
-V, --version print version and exit -V, --version print version and exit
-h, --help print this message and exit -h, --help print this message and exit
Tasks options Tasks options
--all reset options to execute all tasks --all reset options to execute all tasks
--none reset options to execute no task --none reset options to execute no task
--[no-]etc copy of /etc (default: no) --[no-]etc copy of /etc (default: no)
--[no-]dpkg-full copy of /var/lib/dpkg (default: no) --[no-]dpkg-full copy of /var/lib/dpkg (default: no)
--[no-]dpkg-status copy of /var/lib/dpkg/status (default: yes) --[no-]dpkg-status copy of /var/lib/dpkg/status (default: yes)
--[no-]apt-states copy of apt extended states (default: yes) --[no-]apt-states copy of apt extended states (default: yes)
--[no-]apt-config copy of apt configuration (default: yes) --[no-]apt-config copy of apt configuration (default: yes)
--[no-]packages copy of dpkg selections (default: yes) --[no-]packages copy of dpkg selections (default: yes)
--[no-]processes copy of process list (default: yes) --[no-]processes copy of process list (default: yes)
--[no-]uname copy of uname value (default: yes) --[no-]uname copy of uname value (default: yes)
--[no-]uptime copy of uptime value (default: yes) --[no-]uptime copy of uptime value (default: yes)
--[no-]netstat copy of netstat (default: yes) --[no-]netstat copy of netstat (default: yes)
--[no-]netcfg copy of network configuration (default: yes) --[no-]netcfg copy of network configuration (default: yes)
--[no-]iptables copy of iptables (default: yes) --[no-]iptables copy of iptables (default: yes)
--[no-]sysctl copy of sysctl values (default: yes) --[no-]sysctl copy of sysctl values (default: yes)
--[no-]virsh copy of virsh list (default: yes) --[no-]virsh copy of virsh list (default: yes)
--[no-]lxc copy of lxc list (default: yes) --[no-]lxc copy of lxc list (default: yes)
--[no-]disks copy of MBR and partitions (default: yes) --[no-]disks copy of MBR and partitions (default: yes)
--[no-]mount copy of mount points (default: yes) --[no-]mount copy of mount points (default: yes)
--[no-]df copy of disk usage (default: yes) --[no-]df copy of disk usage (default: yes)
--[no-]dmesg copy of dmesg (default: yes) --[no-]dmesg copy of dmesg (default: yes)
--[no-]mysql copy of mysql processes (default: yes) --[no-]mysql-processes copy of mysql processes (default: yes)
--[no-]systemctl copy of systemd services states (default: yes) --[no-]mysql-summary copy of mysql summary (default: yes)
--[no-]systemctl copy of systemd services states (default: yes)
Tasks options order matters. They are evaluated from left to right. Tasks options order matters. They are evaluated from left to right.
Examples : Examples :
* "[…] --none --uname" will do only the uname task * "[…] --none --uname" will do only the uname task
* "[…] --all --no-etc" will do everything but the etc task * "[…] --all --no-etc" will do everything but the etc task
* "[…] --etc --none --mysql" will do only the mysql task * "[…] --etc --none --mysql-summary" will do only the mysql task
END END
} }
debug() { debug() {
if [ "${VERBOSE}" = "1" ]; then if [ "${VERBOSE}" = "1" ]; then
echo "$1" msg="${1:-$(cat /dev/stdin)}"
echo "${msg}"
fi fi
} }
@ -101,7 +103,7 @@ task_etc() {
rsync_bin=$(command -v rsync) rsync_bin=$(command -v rsync)
if [ -n "${rsync_bin}" ]; then if [ -n "${rsync_bin}" ]; then
last_result=$(${rsync_bin} -ah --itemize-changes --exclude=.git /etc "${dump_dir}/") last_result=$(${rsync_bin} -ah --itemize-changes --exclude=.git --delete /etc "${dump_dir}/")
last_rc=$? last_rc=$?
if [ ${last_rc} -eq 0 ]; then if [ ${last_rc} -eq 0 ]; then
@ -185,48 +187,53 @@ task_dpkg_full() {
apt_config_bin=$(command -v apt-config) apt_config_bin=$(command -v apt-config)
if [ -n "${apt_config_bin}" ]; then if [ -n "${apt_config_bin}" ]; then
# will do something like `dir_state_status='/var/lib/dpkg/status'`
eval "$(${apt_config_bin} shell dir_state_status Dir::State::status)" eval "$(${apt_config_bin} shell dir_state_status Dir::State::status)"
fi fi
dpkg_dir=$(dirname "${dir_state_status}") dpkg_dir=$(dirname "${dir_state_status}")
last_result=$(mkdir -p "${dump_dir}${dpkg_dir}" && chmod -R 755 "${dump_dir}${dpkg_dir}") if [ -d "${dpkg_dir}" ]; then
last_rc=$? last_result=$(mkdir -p "${dump_dir}${dpkg_dir}" && chmod -R 755 "${dump_dir}${dpkg_dir}")
if [ ${last_rc} -eq 0 ]; then
debug "* mkdir/chmod OK"
else
debug "* mkdir/chmod ERROR"
debug "${last_result}"
rc=10
fi
rsync_bin=$(command -v rsync)
if [ -n "${rsync_bin}" ]; then
last_result=$(${rsync_bin} -ah --itemize-changes --exclude='*-old' "${dpkg_dir}/" "${dump_dir}${dpkg_dir}/")
last_rc=$? last_rc=$?
if [ ${last_rc} -eq 0 ]; then if [ ${last_rc} -eq 0 ]; then
debug "* rsync OK" debug "* mkdir/chmod OK"
else else
debug "* rsync ERROR :" debug "* mkdir/chmod ERROR"
debug "${last_result}" debug "${last_result}"
rc=10 rc=10
fi fi
rsync_bin=$(command -v rsync)
if [ -n "${rsync_bin}" ]; then
last_result=$(${rsync_bin} -ah --itemize-changes --exclude='*-old' "${dpkg_dir}/" "${dump_dir}${dpkg_dir}/")
last_rc=$?
if [ ${last_rc} -eq 0 ]; then
debug "* rsync OK"
else
debug "* rsync ERROR :"
debug "${last_result}"
rc=10
fi
else
debug "* rsync not found"
last_result=$(cp -r "${dpkg_dir}/*" "${dump_dir}${dpkg_dir}/" && rm -rf "${dump_dir}${dpkg_dir}/*-old")
last_rc=$?
if [ ${last_rc} -eq 0 ]; then
debug "* cp OK"
else
debug "* cp ERROR :"
debug "${last_result}"
rc=10
fi
fi
else else
debug "* rsync not found" debug "* ${dpkg_dir} not found"
last_result=$(cp -r "${dpkg_dir}/*" "${dump_dir}${dpkg_dir}/" && rm -rf "${dump_dir}${dpkg_dir}/*-old")
last_rc=$?
if [ ${last_rc} -eq 0 ]; then
debug "* cp OK"
else
debug "* cp ERROR :"
debug "${last_result}"
rc=10
fi
fi fi
} }
@ -238,18 +245,23 @@ task_dpkg_status() {
apt_config_bin=$(command -v apt-config) apt_config_bin=$(command -v apt-config)
if [ -n "${apt_config_bin}" ]; then if [ -n "${apt_config_bin}" ]; then
# will do something like `dir_state_status='/var/lib/dpkg/status'`
eval "$(${apt_config_bin} shell dir_state_status Dir::State::status)" eval "$(${apt_config_bin} shell dir_state_status Dir::State::status)"
fi fi
last_result=$(cp "${dir_state_status}" "${dump_dir}/dpkg-status.txt") if [ -f "${dir_state_status}" ]; then
last_rc=$? last_result=$(cp "${dir_state_status}" "${dump_dir}/dpkg-status.txt")
last_rc=$?
if [ ${last_rc} -eq 0 ]; then if [ ${last_rc} -eq 0 ]; then
debug "* cp OK" debug "* cp OK"
else
debug "* cp ERROR :"
debug "${last_result}"
rc=10
fi
else else
debug "* cp ERROR :" debug "* ${dir_state_status} not found"
debug "${last_result}"
rc=10
fi fi
} }
@ -425,28 +437,55 @@ task_iptables() {
debug "Task: iptables" debug "Task: iptables"
iptables_bin=$(command -v iptables) iptables_bin=$(command -v iptables)
ip6tables_bin=$(command -v ip6tables)
if [ -n "${iptables_bin}" ]; then if [ -n "${iptables_bin}" ]; then
last_result=$({ ${iptables_bin} -L -n -v; ${iptables_bin} -t filter -L -n -v; } > "${dump_dir}/iptables-v.txt") last_result=$({
printf "#### iptables --list ###############################\n"
${iptables_bin} --list --numeric --verbose --line-numbers
printf "\n### iptables --table nat --list ####################\n"
${iptables_bin} --table nat --list --numeric --verbose --line-numbers
printf "\n#### iptables --table mangle --list ################\n"
${iptables_bin} --table mangle --list --numeric --verbose --line-numbers
if [ -n "${ip6tables_bin}" ]; then
printf "\n#### ip6tables --list ##############################\n"
${ip6tables_bin} --list --numeric --verbose --line-numbers
printf "\n#### ip6tables --table mangle --list ###############\n"
${ip6tables_bin} --table mangle --list --numeric --verbose --line-numbers
fi
} > "${dump_dir}/iptables-v.txt") 2> "${dump_dir}/iptables-v.err"
last_rc=$? last_rc=$?
if [ ${last_rc} -eq 0 ]; then if [ ${last_rc} -eq 0 ]; then
debug "* iptables -v OK" debug "* iptables -v OK"
else else
debug "* iptables -v ERROR" debug "* iptables -v ERROR"
debug "${last_result}" debug "$(cat ${dump_dir}/iptables-v.err)"
# Ignore errors because we don't know if this is nft related or a real error # Ignore errors because we don't know if this is nft related or a real error
# rc=10 # rc=10
fi fi
last_result=$({ ${iptables_bin} -L -n; ${iptables_bin} -t filter -L -n; } > "${dump_dir}/iptables.txt") last_result=$({
printf "#### iptables --list ###############################\n"
${iptables_bin} --list --numeric
printf "\n### iptables --table nat --list ####################\n"
${iptables_bin} --table nat --list --numeric
printf "\n#### iptables --table mangle --list ################\n"
${iptables_bin} --table mangle --list --numeric
if [ -n "${ip6tables_bin}" ]; then
printf "\n#### ip6tables --list ##############################\n"
${ip6tables_bin} --list --numeric
printf "\n#### ip6tables --table mangle --list ###############\n"
${ip6tables_bin} --table mangle --list --numeric
fi
} > "${dump_dir}/iptables.txt") 2> "${dump_dir}/iptables.err"
last_rc=$? last_rc=$?
if [ ${last_rc} -eq 0 ]; then if [ ${last_rc} -eq 0 ]; then
debug "* iptables OK" debug "* iptables OK"
else else
debug "* iptables ERROR" debug "* iptables ERROR"
debug "${last_result}" debug "$(cat ${dump_dir}/iptables.err)"
# Ignore errors because we don't know if this is nft related or a real error # Ignore errors because we don't know if this is nft related or a real error
# rc=10 # rc=10
fi fi
@ -457,14 +496,14 @@ task_iptables() {
iptables_save_bin=$(command -v iptables-save) iptables_save_bin=$(command -v iptables-save)
if [ -n "${iptables_save_bin}" ]; then if [ -n "${iptables_save_bin}" ]; then
last_result=$(${iptables_save_bin} > "${dump_dir}/iptables-save.txt") ${iptables_save_bin} > "${dump_dir}/iptables-save.txt" 2> "${dump_dir}/iptables-save.err"
last_rc=$? last_rc=$?
if [ ${last_rc} -eq 0 ]; then if [ ${last_rc} -eq 0 ]; then
debug "* iptables-save OK" debug "* iptables-save OK"
else else
debug "* iptables-save ERROR" debug "* iptables-save ERROR"
debug "${last_result}" debug "$(cat ${dump_dir}/iptables-save.err)"
# Ignore errors because we don't know if this is nft related or a real error # Ignore errors because we don't know if this is nft related or a real error
# rc=10 # rc=10
fi fi
@ -475,14 +514,14 @@ task_iptables() {
nft_bin=$(command -v nft) nft_bin=$(command -v nft)
if [ -n "${nft_bin}" ]; then if [ -n "${nft_bin}" ]; then
last_result=$(${nft_bin} list ruleset > "${dump_dir}/nft-ruleset.txt") ${nft_bin} list ruleset > "${dump_dir}/nft-ruleset.txt" 2> "${dump_dir}/nft-ruleset.err"
last_rc=$? last_rc=$?
if [ ${last_rc} -eq 0 ]; then if [ ${last_rc} -eq 0 ]; then
debug "* nft ruleset OK" debug "* nft ruleset OK"
else else
debug "* nft ruleset ERROR" debug "* nft ruleset ERROR"
debug "${last_result}" debug "$(cat ${dump_dir}/nft-ruleset.err)"
rc=10 rc=10
fi fi
fi fi
@ -646,7 +685,7 @@ task_df() {
df_bin=$(command -v df) df_bin=$(command -v df)
if [ -n "${df_bin}" ]; then if [ -n "${df_bin}" ]; then
last_result=$(${df_bin} --portability > "${dump_dir}/df.txt") last_result=$(${df_bin} --portability > "${dump_dir}/df.txt" 2>&1)
last_rc=$? last_rc=$?
if [ ${last_rc} -eq 0 ]; then if [ ${last_rc} -eq 0 ]; then
@ -690,15 +729,20 @@ task_mysql_processes() {
if [ -n "${mysqladmin_bin}" ]; then if [ -n "${mysqladmin_bin}" ]; then
# Look for local MySQL or MariaDB process # Look for local MySQL or MariaDB process
if pgrep mysqld > /dev/null || pgrep mariadbd > /dev/null; then if pgrep mysqld > /dev/null || pgrep mariadbd > /dev/null; then
last_result=$(${mysqladmin_bin} --verbose processlist > "${dump_dir}/mysql-processlist.txt") if ${mysqladmin_bin} ping > /dev/null 2>&1; then
last_rc=$? ${mysqladmin_bin} --verbose processlist > "${dump_dir}/mysql-processlist.txt" 2> "${dump_dir}/mysql-processlist.err"
last_rc=$?
if [ ${last_rc} -eq 0 ]; then if [ ${last_rc} -eq 0 ]; then
debug "* mysqladmin OK" debug "* mysqladmin OK"
else
debug "* mysqladmin ERROR"
debug < "${dump_dir}/mysql-processlist.err"
rm "${dump_dir}/mysql-processlist.err"
rc=10
fi
else else
debug "* mysqladmin ERROR" debug "* unable to ping with mysqladmin"
debug "${last_result}"
rc=10
fi fi
else else
debug "* no mysqld or mariadbd process is running" debug "* no mysqld or mariadbd process is running"
@ -708,6 +752,41 @@ task_mysql_processes() {
fi fi
} }
task_mysql_summary() {
debug "Task: MySQL summary"
mysqladmin_bin=$(command -v mysqladmin)
pt_mysql_summary_bin=$(command -v pt-mysql-summary)
if [ -n "${mysqladmin_bin}" ] && [ -n "${pt_mysql_summary_bin}" ]; then
# Look for local MySQL or MariaDB process
if pgrep mysqld > /dev/null || pgrep mariadbd > /dev/null; then
if ${mysqladmin_bin} ping > /dev/null 2>&1; then
# important to set sleep to 0
# because we don't want to block
# even if we lose some insight.
${pt_mysql_summary_bin} --sleep 0 > "${dump_dir}/mysql-summary.txt" 2> "${dump_dir}/mysql-summary.err"
last_rc=$?
if [ ${last_rc} -eq 0 ]; then
debug "* pt-mysql-summary OK"
else
debug "* pt-mysql-summary ERROR"
debug < "${dump_dir}/mysql-summary.err"
rm "${dump_dir}/mysql-summary.err"
rc=10
fi
else
debug "* unable to ping with mysqladmin"
fi
else
debug "* no mysqld or mariadbd process is running"
fi
else
debug "* pt-mysql-summary not found"
fi
}
task_systemctl() { task_systemctl() {
debug "Task: Systemd services" debug "Task: Systemd services"
@ -729,6 +808,10 @@ task_systemctl() {
fi fi
} }
clean_empty_error_file() {
find "${dump_dir}" -type f -name "*.err" -size 0 -delete
}
main() { main() {
if [ -z "${dump_dir}" ]; then if [ -z "${dump_dir}" ]; then
echo "ERROR: You must provide the --dump-dir argument" >&2 echo "ERROR: You must provide the --dump-dir argument" >&2
@ -804,10 +887,14 @@ main() {
if [ "${TASK_MYSQL_PROCESSES}" -eq 1 ]; then if [ "${TASK_MYSQL_PROCESSES}" -eq 1 ]; then
task_mysql_processes task_mysql_processes
fi fi
if [ "${TASK_MYSQL_SUMMARY}" -eq 1 ]; then
task_mysql_summary
fi
if [ "${TASK_SYSTEMCTL}" -eq 1 ]; then if [ "${TASK_SYSTEMCTL}" -eq 1 ]; then
task_systemctl task_systemctl
fi fi
clean_empty_error_file
debug "=> Your dump is available at ${dump_dir}" debug "=> Your dump is available at ${dump_dir}"
exit ${rc} exit ${rc}
@ -912,6 +999,7 @@ while :; do
TASK_DF \ TASK_DF \
TASK_DMESG \ TASK_DMESG \
TASK_MYSQL_PROCESSES \ TASK_MYSQL_PROCESSES \
TASK_MYSQL_SUMMARY \
TASK_SYSTEMCTL TASK_SYSTEMCTL
do do
eval "${option}=1" eval "${option}=1"
@ -940,6 +1028,7 @@ while :; do
TASK_DF \ TASK_DF \
TASK_DMESG \ TASK_DMESG \
TASK_MYSQL_PROCESSES \ TASK_MYSQL_PROCESSES \
TASK_MYSQL_SUMMARY \
TASK_SYSTEMCTL TASK_SYSTEMCTL
do do
eval "${option}=0" eval "${option}=0"
@ -1086,6 +1175,13 @@ while :; do
TASK_MYSQL_PROCESSES=0 TASK_MYSQL_PROCESSES=0
;; ;;
--mysql-summary)
TASK_MYSQL_SUMMARY=1
;;
--no-mysql-summary)
TASK_MYSQL_SUMMARY=0
;;
--systemctl) --systemctl)
TASK_SYSTEMCTL=1 TASK_SYSTEMCTL=1
;; ;;
@ -1135,6 +1231,7 @@ done
: "${TASK_DF:=1}" : "${TASK_DF:=1}"
: "${TASK_DMESG:=1}" : "${TASK_DMESG:=1}"
: "${TASK_MYSQL_PROCESSES:=1}" : "${TASK_MYSQL_PROCESSES:=1}"
: "${TASK_MYSQL_SUMMARY:=1}"
: "${TASK_SYSTEMCTL:=1}" : "${TASK_SYSTEMCTL:=1}"
export LC_ALL=C export LC_ALL=C