Linux server.kiran-academy.com 3.10.0-1160.108.1.el7.x86_64 #1 SMP Thu Jan 25 16:17:31 UTC 2024 x86_64
Apache/2.4.57 (Unix) OpenSSL/1.0.2k-fips
: 194.233.91.196 | : 216.73.216.172
Cant Read [ /etc/named.conf ]
7.4.32
finalho
www.github.com/MadExploits
Terminal
AUTO ROOT
Adminer
Backdoor Destroyer
Linux Exploit
Lock Shell
Lock File
Create User
CREATE RDP
PHP Mailer
BACKCONNECT
UNLOCK SHELL
HASH IDENTIFIER
CPANEL RESET
CREATE WP USER
README
+ Create Folder
+ Create File
/
usr /
bin /
[ HOME SHELL ]
Name
Size
Permission
Action
7z
40
B
-rwxr-xr-x
7za
41
B
-rwxr-xr-x
GET
14.71
KB
-rwxr-xr-x
HEAD
14.71
KB
-rwxr-xr-x
Magick-config
1.23
KB
-rwxr-xr-x
MagickCore-config
1.24
KB
-rwxr-xr-x
MagickWand-config
1.25
KB
-rwxr-xr-x
Mail
383.67
KB
-rwxr-xr-x
POST
14.71
KB
-rwxr-xr-x
Wand-config
1.23
KB
-rwxr-xr-x
[
40.52
KB
-rwxr-xr-x
a2p
105.32
KB
-rwxr-xr-x
aclocal
35.87
KB
-rwxr-xr-x
aclocal-1.13
35.87
KB
-rwxr-xr-x
addr2line
28.42
KB
-rwxr-xr-x
agentxtrap
19.16
KB
-rwxr-xr-x
alias
29
B
-rwxr-xr-x
altermime
108.35
KB
-rwxr-xr-x
amavisd-agent
13.12
KB
-rwxr-xr-x
amavisd-nanny
11.72
KB
-rwxr-xr-x
amavisd-release
12.35
KB
-rwxr-xr-x
amavisd-signer
36.85
KB
-rwxr-xr-x
amavisd-submit
9.09
KB
-rwxr-xr-x
animate
7.04
KB
-rwxr-xr-x
apr-1-config
6.76
KB
-rwxr-xr-x
apropos
45.49
KB
-rwxr-xr-x
apu-1-config
5.97
KB
-rwxr-xr-x
ar
61.22
KB
-rwxr-xr-x
arch
32.3
KB
-rwxr-xr-x
aria_chk
4.56
MB
-rwxr-xr-x
aria_dump_log
4.36
MB
-rwxr-xr-x
aria_ftdump
4.37
MB
-rwxr-xr-x
aria_pack
4.4
MB
-rwxr-xr-x
aria_read_log
4.52
MB
-rwxr-xr-x
arj
199.56
KB
-rwxr-xr-x
arjdisp
12.55
KB
-rwxr-xr-x
arpaname
7.03
KB
-rwxr-xr-x
as
377.36
KB
-rwxr-xr-x
aserver
28.21
KB
-rwxr-xr-x
aspell
163.35
KB
-rwxr-xr-x
at
51.73
KB
-rwsr-xr-x
atq
51.73
KB
-rwsr-xr-x
atrm
51.73
KB
-rwsr-xr-x
audit2allow
14.21
KB
-rwxr-xr-x
audit2why
14.21
KB
-rwxr-xr-x
aulast
15.48
KB
-rwxr-xr-x
aulastlog
11.35
KB
-rwxr-xr-x
ausyscall
11.18
KB
-rwxr-xr-x
autoconf
14.42
KB
-rwxr-xr-x
autoheader
8.33
KB
-rwxr-xr-x
autom4te
31.43
KB
-rwxr-xr-x
automake
246.44
KB
-rwxr-xr-x
automake-1.13
246.44
KB
-rwxr-xr-x
autoreconf
20.57
KB
-rwxr-xr-x
autoscan
16.72
KB
-rwxr-xr-x
autoupdate
33.08
KB
-rwxr-xr-x
auvirt
31.93
KB
-rwxr-xr-x
awk
418.54
KB
-rwxr-xr-x
base64
36.48
KB
-rwxr-xr-x
basename
28.35
KB
-rwxr-xr-x
bash
941.93
KB
-rwxr-xr-x
bashbug
6.8
KB
-rwxr-xr-x
bashbug-64
6.8
KB
-rwxr-xr-x
batch
137
B
-rwxr-xr-x
bc
81.47
KB
-rwxr-xr-x
bdftopcf
44.19
KB
-rwxr-xr-x
bdftruncate
11.2
KB
-rwxr-xr-x
bg
26
B
-rwxr-xr-x
bison
393.34
KB
-rwxr-xr-x
bond2team
22.74
KB
-rwxr-xr-x
bootctl
68.99
KB
-rwxr-xr-x
bunzip2
31.89
KB
-rwxr-xr-x
busctl
403.2
KB
-rwxr-xr-x
bzcat
31.89
KB
-rwxr-xr-x
bzcmp
2.08
KB
-rwxr-xr-x
bzdiff
2.08
KB
-rwxr-xr-x
bzgrep
1.64
KB
-rwxr-xr-x
bzip2
31.89
KB
-rwxr-xr-x
bzip2recover
15.24
KB
-rwxr-xr-x
bzless
1.23
KB
-rwxr-xr-x
bzmore
1.23
KB
-rwxr-xr-x
c++
754.59
KB
-rwxr-xr-x
c++filt
27.92
KB
-rwxr-xr-x
c2ph
35.75
KB
-rwxr-xr-x
c89
224
B
-rwxr-xr-x
c99
215
B
-rwxr-xr-x
ca-legacy
1.6
KB
-rwxr-xr-x
cabextract
68.7
KB
-rwxr-xr-x
cairo-sphinx
69.99
KB
-rwxr-xr-x
cal
36.8
KB
-rwxr-xr-x
captoinfo
64.26
KB
-rwxr-xr-x
cat
52.81
KB
-rwxr-xr-x
catchsegv
3.26
KB
-rwxr-xr-x
catman
36.75
KB
-rwxr-xr-x
cc
750.59
KB
-rwxr-xr-x
ccmake
3.95
MB
-rwxr-xr-x
ccmake3
5.83
MB
-rwxr-xr-x
cd
26
B
-rwxr-xr-x
centrino-decode
6.13
KB
-rwxr-xr-x
certutil
179.75
KB
-rwxr-xr-x
certwatch
15.38
KB
-rwxr-xr-x
chacl
15.27
KB
-rwxr-xr-x
chage
72.16
KB
-rwsr-xr-x
chardetect
307
B
-rwxr-xr-x
chattr
11.27
KB
-rwxr-xr-x
chcat
13.12
KB
-rwxr-xr-x
chcon
61.46
KB
-rwxr-xr-x
checkmodule
400.48
KB
-rwxr-xr-x
checkpolicy
412.46
KB
-rwxr-xr-x
chfn
23.41
KB
-rws--x--x
chgrp
61.32
KB
-rwxr-xr-x
chmem
40.43
KB
-rwxr-xr-x
chmod
57.21
KB
-rwxr-xr-x
chown
61.36
KB
-rwxr-xr-x
chronyc
85.03
KB
-rwxr-xr-x
chrt
32.17
KB
-rwxr-xr-x
chsh
23.32
KB
-rws--x--x
chvt
11.14
KB
-rwxr-xr-x
cifsiostat
48.5
KB
-rwxr-xr-x
cksum
32.38
KB
-rwxr-xr-x
clambc
139.76
KB
-rwxr-xr-x
clamconf
139.43
KB
-rwxr-xr-x
clamdscan
155.77
KB
-rwxr-xr-x
clamdtop
155.74
KB
-rwxr-xr-x
clamscan
163.64
KB
-rwxr-xr-x
clamsubmit
147.73
KB
-rwxr-xr-x
clear
7.02
KB
-rwxr-xr-x
clusterdb
53.22
KB
-rwxr-xr-x
cmake
3.9
MB
-rwxr-xr-x
cmake3
6.21
MB
-rwxr-xr-x
cmp
44.14
KB
-rwxr-xr-x
cmsutil
110.59
KB
-rwxr-xr-x
col
23.88
KB
-rwxr-xr-x
colcrt
11.29
KB
-rwxr-xr-x
colrm
23.77
KB
-rwxr-xr-x
column
27.99
KB
-rwxr-xr-x
comm
36.55
KB
-rwxr-xr-x
command
31
B
-rwxr-xr-x
compare
11.09
KB
-rwxr-xr-x
compile_et
1.45
KB
-rwxr-xr-x
composite
7.04
KB
-rwxr-xr-x
conjure
7.04
KB
-rwxr-xr-x
consolehelper
6.95
KB
-rwxr-xr-x
convert
7.04
KB
-rwxr-xr-x
coredumpctl
154.52
KB
-rwxr-xr-x
cp
151.55
KB
-rwxr-xr-x
cpack
4.16
MB
-rwxr-xr-x
cpack3
6.31
MB
-rwxr-xr-x
cpio
142.55
KB
-rwxr-xr-x
cpp
754.58
KB
-rwxr-xr-x
cpulimit
23.78
KB
-rwxr-xr-x
cpupower
66.34
KB
-rwxr-xr-x
crc32
794
B
-rwxr-xr-x
createdb
53.27
KB
-rwxr-xr-x
createlang
57.09
KB
-rwxr-xr-x
createuser
57.63
KB
-rwxr-xr-x
crlutil
127
KB
-rwxr-xr-x
crontab
56.23
KB
-rwsr-xr-x
csplit
48.82
KB
-rwxr-xr-x
csslint-0.6
19.55
KB
-rwxr-xr-x
ctest
4.96
MB
-rwxr-xr-x
ctest3
7.14
MB
-rwxr-xr-x
curl
152.99
KB
-rwxr-xr-x
curl-config
4.08
KB
-rwxr-xr-x
cut
40.61
KB
-rwxr-xr-x
cvtsudoers
241.7
KB
-rwxr-xr-x
cxpm
28.05
KB
-rwxr-xr-x
date
60.74
KB
-rwxr-xr-x
db_archive
11.23
KB
-rwxr-xr-x
db_checkpoint
11.3
KB
-rwxr-xr-x
db_deadlock
11.31
KB
-rwxr-xr-x
db_dump
15.33
KB
-rwxr-xr-x
db_dump185
64.45
KB
-rwxr-xr-x
db_hotbackup
15.34
KB
-rwxr-xr-x
db_load
27.51
KB
-rwxr-xr-x
db_log_verify
15.34
KB
-rwxr-xr-x
db_printlog
32.26
KB
-rwxr-xr-x
db_recover
11.33
KB
-rwxr-xr-x
db_replicate
15.34
KB
-rwxr-xr-x
db_stat
15.26
KB
-rwxr-xr-x
db_tuner
19.33
KB
-rwxr-xr-x
db_upgrade
11.23
KB
-rwxr-xr-x
db_verify
11.25
KB
-rwxr-xr-x
dbilogstrip
1.43
KB
-rwxr-xr-x
dbinfo
3.29
KB
-rwxr-xr-x
dbiprof
6.15
KB
-rwxr-xr-x
dbiproxy
5.35
KB
-rwxr-xr-x
dbus-binding-tool
94.49
KB
-rwxr-xr-x
dbus-cleanup-sockets
11
KB
-rwxr-xr-x
dbus-daemon
218
KB
-rwxr-xr-x
dbus-monitor
23.13
KB
-rwxr-xr-x
dbus-run-session
14.97
KB
-rwxr-xr-x
dbus-send
27.07
KB
-rwxr-xr-x
dbus-test-tool
23.13
KB
-rwxr-xr-x
dbus-update-activation-environ...
15.02
KB
-rwxr-xr-x
dbus-uuidgen
10.98
KB
-rwxr-xr-x
dc
44.33
KB
-rwxr-xr-x
dd
73.14
KB
-rwxr-xr-x
deallocvt
11.16
KB
-rwxr-xr-x
debuginfo-install
7.88
KB
-rwxr-xr-x
delv
39.98
KB
-rwxr-xr-x
derb
19.95
KB
-rwxr-xr-x
df
102.55
KB
-rwxr-xr-x
dgawk
502.12
KB
-rwxr-xr-x
diff
195.46
KB
-rwxr-xr-x
diff3
60.65
KB
-rwxr-xr-x
dig
146.96
KB
-rwxr-xr-x
dir
114.85
KB
-rwxr-xr-x
dircolors
40.44
KB
-rwxr-xr-x
dirname
28.31
KB
-rwxr-xr-x
display
7.04
KB
-rwxr-xr-x
dm_date
5.47
KB
-rwxr-xr-x
dm_zdump
4.95
KB
-rwxr-xr-x
dmesg
48.52
KB
-rwxr-xr-x
dnsdomainname
15.41
KB
-rwxr-xr-x
domainname
15.41
KB
-rwxr-xr-x
doveadm
596.59
KB
-rwxr-xr-x
doveconf
161.41
KB
-rwxr-xr-x
dracut
55.83
KB
-rwxr-xr-x
dropdb
53.08
KB
-rwxr-xr-x
droplang
57.08
KB
-rwxr-xr-x
dropuser
53.05
KB
-rwxr-xr-x
dsync
596.59
KB
-rwxr-xr-x
dtrace
16.66
KB
-rwxr-xr-x
du
110.34
KB
-rwxr-xr-x
dumpkeys
77.51
KB
-rwxr-xr-x
dumpsexp
15.18
KB
-rwxr-xr-x
dwp
3.03
MB
-rwxr-xr-x
easy_install-3.6
234
B
-rwxr-xr-x
echo
32.31
KB
-rwxr-xr-x
ecpg
737.83
KB
-rwxr-xr-x
ed2k-link
71.57
KB
-rwxr-xr-x
egrep
290
B
-rwxr-xr-x
eject
48.81
KB
-rwxr-xr-x
elfedit
32.18
KB
-rwxr-xr-x
enchant
15.29
KB
-rwxr-xr-x
enchant-lsmod
11.13
KB
-rwxr-xr-x
env
28.33
KB
-rwxr-xr-x
envsubst
36.01
KB
-rwxr-xr-x
eqn
144.41
KB
-rwxr-xr-x
ex
906.45
KB
-rwxr-xr-x
expand
32.48
KB
-rwxr-xr-x
expr
36.53
KB
-rwxr-xr-x
factor
93.29
KB
-rwxr-xr-x
fallocate
27.84
KB
-rwxr-xr-x
false
28.25
KB
-rwxr-xr-x
fc
26
B
-rwxr-xr-x
fc-cache
132
B
-rwxr-xr-x
fc-cache-64
15.45
KB
-rwxr-xr-x
fc-cat
15.4
KB
-rwxr-xr-x
fc-conflist
11.2
KB
-rwxr-xr-x
fc-list
11.26
KB
-rwxr-xr-x
fc-match
11.32
KB
-rwxr-xr-x
fc-pattern
11.26
KB
-rwxr-xr-x
fc-query
11.23
KB
-rwxr-xr-x
fc-scan
11.27
KB
-rwxr-xr-x
fc-validate
11.27
KB
-rwxr-xr-x
fcat
23.82
KB
-rwxr-xr-x
fg
26
B
-rwxr-xr-x
fgconsole
11.16
KB
-rwxr-xr-x
fgrep
290
B
-rwxr-xr-x
filan
89.13
KB
-rwxr-xr-x
file
19.3
KB
-rwxr-xr-x
find
194.63
KB
-rwxr-xr-x
find-repos-of-install
4.27
KB
-rwxr-xr-x
find2perl
23.06
KB
-rwxr-xr-x
findmnt
58.38
KB
-rwxr-xr-x
fipscheck
15.37
KB
-rwxr-xr-x
fipshmac
11.3
KB
-rwxr-xr-x
firewall-cmd
113.48
KB
-rwxr-xr-x
firewall-offline-cmd
101.18
KB
-rwxr-xr-x
flex
318.07
KB
-rwxr-xr-x
flex++
318.07
KB
-rwxr-xr-x
flock
23.88
KB
-rwxr-xr-x
fmt
36.48
KB
-rwxr-xr-x
fold
36.46
KB
-rwxr-xr-x
fonttosfnt
35.99
KB
-rwxr-xr-x
formail
43.68
KB
-rwxr-xr-x
free
19.33
KB
-rwxr-xr-x
freetype-config
4.39
KB
-rwxr-xr-x
freeze
23.82
KB
-rwxr-xr-x
freshclam
39.23
KB
-rwxr-xr-x
fribidi
16.09
KB
-rwxr-xr-x
funzip
31.38
KB
-rwxr-xr-x
g++
754.59
KB
-rwxr-xr-x
galera_new_cluster
917
B
-rwxr-xr-x
galera_recovery
3.28
KB
-rwxr-xr-x
gapplication
19.48
KB
-rwxr-xr-x
garb-systemd
1.2
KB
-rwxr-xr-x
garbd
2.2
MB
-rwxr-xr-x
gawk
418.54
KB
-rwxr-xr-x
gcc
750.59
KB
-rwxr-xr-x
gcc-ar
26.45
KB
-rwxr-xr-x
gcc-nm
26.45
KB
-rwxr-xr-x
gcc-ranlib
26.45
KB
-rwxr-xr-x
gcov
307.45
KB
-rwxr-xr-x
gdbus
40.17
KB
-rwxr-xr-x
gdbus-codegen
2
KB
-rwxr-xr-x
gdk-pixbuf-query-loaders-64
15.38
KB
-rwxr-xr-x
gdk-pixbuf-thumbnailer
15.59
KB
-rwxr-xr-x
genbrk
15.75
KB
-rwxr-xr-x
gencat
22.11
KB
-rwxr-xr-x
gencfu
11.64
KB
-rwxr-xr-x
gencnval
19.71
KB
-rwxr-xr-x
gendict
24.01
KB
-rwxr-xr-x
genkey
43.13
KB
-rwxr-xr-x
genl-ctrl-list
11.27
KB
-rwxr-xr-x
genrb
95.2
KB
-rwxr-xr-x
geoiplookup
15.28
KB
-rwxr-xr-x
geoiplookup6
11.16
KB
-rwxr-xr-x
geoipupdate
31.28
KB
-rwxr-xr-x
geqn
144.41
KB
-rwxr-xr-x
getconf
22.02
KB
-rwxr-xr-x
getent
26.39
KB
-rwxr-xr-x
getfacl
24.29
KB
-rwxr-xr-x
getkeycodes
11.16
KB
-rwxr-xr-x
getopt
15.38
KB
-rwxr-xr-x
getopts
31
B
-rwxr-xr-x
gettext
35.94
KB
-rwxr-xr-x
gettext.sh
4.52
KB
-rwxr-xr-x
gio
73.41
KB
-rwxr-xr-x
gio-querymodules-64
11.31
KB
-rwxr-xr-x
git
1.46
MB
-rwxr-xr-x
git-receive-pack
1.46
MB
-rwxr-xr-x
git-shell
717.98
KB
-rwxr-xr-x
git-upload-archive
1.46
MB
-rwxr-xr-x
git-upload-pack
791.45
KB
-rwxr-xr-x
glib-compile-resources
40.25
KB
-rwxr-xr-x
glib-compile-schemas
44.38
KB
-rwxr-xr-x
glib-genmarshal
39.97
KB
-rwxr-xr-x
glib-gettextize
5.25
KB
-rwxr-xr-x
glib-mkenums
27.15
KB
-rwxr-xr-x
gmake
178.47
KB
-rwxr-xr-x
gneqn
271
B
-rwxr-xr-x
gnroff
3.31
KB
-rwxr-xr-x
goaccess
610.99
KB
-rwxr-xr-x
gobject-query
11.13
KB
-rwxr-xr-x
gost-hash
71.57
KB
-rwxr-xr-x
gpasswd
76.57
KB
-rwsr-xr-x
gpg
732.4
KB
-rwxr-xr-x
gpg-agent
289.74
KB
-rwxr-xr-x
gpg-connect-agent
152.69
KB
-rwxr-xr-x
gpg-error
23.18
KB
-rwxr-xr-x
gpg-error-config
1.83
KB
-rwxr-xr-x
gpg-zip
3.23
KB
-rwxr-xr-x
gpg2
732.4
KB
-rwxr-xr-x
gpgconf
140.31
KB
-rwxr-xr-x
gpgparsemail
23.7
KB
-rwxr-xr-x
gpgsplit
48.88
KB
-rwxr-xr-x
gpgv
345.38
KB
-rwxr-xr-x
gpgv2
345.38
KB
-rwxr-xr-x
gpic
180.41
KB
-rwxr-xr-x
gprof
98.41
KB
-rwxr-xr-x
gr2fonttest
23.66
KB
-rwxr-xr-x
grep
155.3
KB
-rwxr-xr-x
gresource
19.55
KB
-rwxr-xr-x
groff
81.63
KB
-rwxr-xr-x
grops
140.85
KB
-rwxr-xr-x
grotty
98.59
KB
-rwxr-xr-x
groups
32.41
KB
-rwxr-xr-x
grub2-editenv
396.07
KB
-rwxr-xr-x
grub2-file
830.36
KB
-rwxr-xr-x
grub2-fstest
1.03
MB
-rwxr-xr-x
grub2-glue-efi
258.85
KB
-rwxr-xr-x
grub2-kbdcomp
1.63
KB
-rwxr-xr-x
grub2-menulst2cfg
241.82
KB
-rwxr-xr-x
grub2-mkfont
287.6
KB
-rwxr-xr-x
grub2-mkimage
379.25
KB
-rwxr-xr-x
grub2-mklayout
264.91
KB
-rwxr-xr-x
grub2-mknetdir
429.8
KB
-rwxr-xr-x
grub2-mkpasswd-pbkdf2
271.29
KB
-rwxr-xr-x
grub2-mkrelpath
258.48
KB
-rwxr-xr-x
grub2-mkrescue
1017.84
KB
-rwxr-xr-x
grub2-mkstandalone
534.32
KB
-rwxr-xr-x
grub2-render-label
835.16
KB
-rwxr-xr-x
grub2-script-check
295.43
KB
-rwxr-xr-x
grub2-syslinux2cfg
763.7
KB
-rwxr-xr-x
gsettings
23.8
KB
-rwxr-xr-x
gsoelim
32.59
KB
-rwxr-xr-x
gss-client
23.17
KB
-rwxr-xr-x
gtar
338.02
KB
-rwxr-xr-x
gtbl
115.96
KB
-rwxr-xr-x
gtester
23.64
KB
-rwxr-xr-x
gtester-report
18.39
KB
-rwxr-xr-x
gtroff
512.96
KB
-rwxr-xr-x
gunzip
2.2
KB
-rwxr-xr-x
gzexe
5.79
KB
-rwxr-xr-x
gzip
98.43
KB
-rwxr-xr-x
h2ph
27.65
KB
-rwxr-xr-x
h2xs
59.15
KB
-rwxr-xr-x
has160-hash
71.57
KB
-rwxr-xr-x
hdsploader
11.18
KB
-rwxr-xr-x
head
40.51
KB
-rwxr-xr-x
hexdump
32.02
KB
-rwxr-xr-x
hmac256
15.67
KB
-rwxr-xr-x
host
127.02
KB
-rwxr-xr-x
hostid
28.32
KB
-rwxr-xr-x
hostname
15.41
KB
-rwxr-xr-x
hostnamectl
318.07
KB
-rwxr-xr-x
hunspell
53.11
KB
-rwxr-xr-x
i386
15.27
KB
-rwxr-xr-x
iceauth
31.73
KB
-rwxr-xr-x
iconv
58.14
KB
-rwxr-xr-x
icu-config
172
B
-rwxr-xr-x
icu-config-64
21.49
KB
-rwxr-xr-x
icuinfo
15.6
KB
-rwxr-xr-x
id
36.52
KB
-rwxr-xr-x
identify
7.06
KB
-rwxr-xr-x
idiag-socket-details
11.34
KB
-rwxr-xr-x
idn
32.45
KB
-rwxr-xr-x
ifnames
4.03
KB
-rwxr-xr-x
igawk
3.11
KB
-rwxr-xr-x
imapsync
338.75
KB
-rwxr-xr-x
import
7.04
KB
-rwxr-xr-x
info
265.19
KB
-rwxr-xr-x
infocmp
56.07
KB
-rwxr-xr-x
infokey
21.47
KB
-rwxr-xr-x
infotocap
64.26
KB
-rwxr-xr-x
innochecksum
3.64
MB
-rwxr-xr-x
install
139.59
KB
-rwxr-xr-x
instmodsh
4.15
KB
-rwxr-xr-x
ionice
23.86
KB
-rwxr-xr-x
iostat
60.77
KB
-rwxr-xr-x
ipcalc
15.05
KB
-rwxr-xr-x
ipcmk
24
KB
-rwxr-xr-x
ipcrm
27.85
KB
-rwxr-xr-x
ipcs
48.37
KB
-rwxr-xr-x
iptables-xml
91.52
KB
-rwxr-xr-x
isosize
23.83
KB
-rwxr-xr-x
ispell
988
B
-rwxr-xr-x
jemalloc.sh
125
B
-rwxr-xr-x
jobs
28
B
-rwxr-xr-x
join
48.75
KB
-rwxr-xr-x
journalctl
537.88
KB
-rwxr-xr-x
kbd_mode
11.16
KB
-rwxr-xr-x
kbdinfo
11.19
KB
-rwxr-xr-x
kbdrate
11.22
KB
-rwxr-xr-x
kdumpctl
33.18
KB
-rwxr-xr-x
kernel-install
4.7
KB
-rwxr-xr-x
keyrand
11.25
KB
-rwxr-xr-x
keyutil
60.84
KB
-rwxr-xr-x
kill
32.82
KB
-rwxr-xr-x
kmod
143.24
KB
-rwxr-xr-x
krb5-config
6.96
KB
-rwxr-xr-x
l4p-tmpl
1.89
KB
-rwxr-xr-x
last
19.11
KB
-rwxr-xr-x
lastb
19.11
KB
-rwxr-xr-x
lastlog
19.15
KB
-rwxr-xr-x
lchfn
15.52
KB
-rwxr-xr-x
lchsh
15.49
KB
-rwxr-xr-x
ld
982.61
KB
-rwxr-xr-x
ld.bfd
982.61
KB
-rwxr-xr-x
ld.gold
5.11
MB
-rwxr-xr-x
ldd
5.18
KB
-rwxr-xr-x
less
154.53
KB
-rwxr-xr-x
lessecho
11.11
KB
-rwxr-xr-x
lesskey
16.66
KB
-rwxr-xr-x
lesspipe.sh
2.24
KB
-rwxr-xr-x
lex
318.07
KB
-rwxr-xr-x
lexgrog
85.09
KB
-rwxr-xr-x
libgcrypt-config
3.78
KB
-rwxr-xr-x
libmcrypt-config
1.38
KB
-rwxr-xr-x
libnetcfg
15.36
KB
-rwxr-xr-x
libpng-config
2.38
KB
-rwxr-xr-x
libpng15-config
2.38
KB
-rwxr-xr-x
link
28.3
KB
-rwxr-xr-x
links
4.37
MB
-rwxr-xr-x
links2
4.37
MB
-rwxr-xr-x
linux-boot-prober
5.85
KB
-rwxr-xr-x
linux32
15.27
KB
-rwxr-xr-x
linux64
15.27
KB
-rwxr-xr-x
ln
57.22
KB
-rwxr-xr-x
loadkeys
110.48
KB
-rwxr-xr-x
loadunimap
23.88
KB
-rwxr-xr-x
locale
37.11
KB
-rwxr-xr-x
localectl
326.16
KB
-rwxr-xr-x
localedef
318.31
KB
-rwxr-xr-x
locate
39.57
KB
-rwx--s--x
lockfile
19.36
KB
-rwxr-sr-x
logger
28.62
KB
-rwxr-xr-x
login
36.38
KB
-rwxr-xr-x
loginctl
489.62
KB
-rwxr-xr-x
logname
28.31
KB
-rwxr-xr-x
look
11.27
KB
-rwxr-xr-x
ls
114.85
KB
-rwxr-xr-x
lsattr
11.25
KB
-rwxr-xr-x
lsblk
79.17
KB
-rwxr-xr-x
lscpu
60.75
KB
-rwxr-xr-x
lsinitrd
6.26
KB
-rwxr-xr-x
lsipc
60.95
KB
-rwxr-xr-x
lslocks
41.01
KB
-rwxr-xr-x
lslogins
52.88
KB
-rwxr-xr-x
lsmem
40.78
KB
-rwxr-xr-x
lsns
36.43
KB
-rwxr-xr-x
lsscsi
56.48
KB
-rwxr-xr-x
lua
15.47
KB
-rwxr-xr-x
luac
119.03
KB
-rwxr-xr-x
lwp-download
8.43
KB
-rwxr-xr-x
lwp-dump
2.73
KB
-rwxr-xr-x
lwp-mirror
2.42
KB
-rwxr-xr-x
lwp-request
14.71
KB
-rwxr-xr-x
lynis
50.57
KB
-rwxr-xr-x
lz4
105.84
KB
-rwxr-xr-x
lz4c
105.84
KB
-rwxr-xr-x
lz4cat
105.84
KB
-rwxr-xr-x
lzop
69.32
KB
-rwxr-xr-x
m4
151.17
KB
-rwxr-xr-x
machinectl
533.95
KB
-rwxr-xr-x
magnet-link
71.57
KB
-rwxr-xr-x
mail
383.67
KB
-rwxr-xr-x
mailq
322.16
KB
-rwxr-xr-x
mailq.postfix
322.16
KB
-rwxr-xr-x
mailstat
5.72
KB
-rwxr-xr-x
mailx
383.67
KB
-rwxr-xr-x
make
178.47
KB
-rwxr-xr-x
makeconv
40.51
KB
-rwxr-xr-x
makedb
18.11
KB
-rwxr-xr-x
man
100.44
KB
-rwxr-xr-x
mandb
122.25
KB
-rwxr-xr-x
manpath
32.55
KB
-rwxr-xr-x
mapscrn
19.78
KB
-rwxr-xr-x
mariadb
4.33
MB
-rwxr-xr-x
mariadb-access
109.33
KB
-rwxr-xr-x
mariadb-admin
3.85
MB
-rwxr-xr-x
mariadb-binlog
4.11
MB
-rwxr-xr-x
mariadb-check
3.85
MB
-rwxr-xr-x
mariadb-convert-table-format
4.12
KB
-rwxr-xr-x
mariadb-dump
3.94
MB
-rwxr-xr-x
mariadb-dumpslow
8.05
KB
-rwxr-xr-x
mariadb-embedded
21.82
MB
-rwxr-xr-x
mariadb-find-rows
3.21
KB
-rwxr-xr-x
mariadb-fix-extensions
1.22
KB
-rwxr-xr-x
mariadb-hotcopy
34.14
KB
-rwxr-xr-x
mariadb-import
3.84
MB
-rwxr-xr-x
mariadb-install-db
21.57
KB
-rwxr-xr-x
mariadb-plugin
3.54
MB
-rwxr-xr-x
mariadb-secure-installation
13.37
KB
-rwxr-xr-x
mariadb-service-convert
2.45
KB
-rwxr-xr-x
mariadb-setpermission
17.13
KB
-rwxr-xr-x
mariadb-show
3.84
MB
-rwxr-xr-x
mariadb-slap
3.86
MB
-rwxr-xr-x
mariadb-tzinfo-to-sql
3.54
MB
-rwxr-xr-x
mariadb-upgrade
3.63
MB
-rwxr-xr-x
mariadb-waitpid
3.53
MB
-rwxr-xr-x
mariadb_config
11.57
KB
-rwxr-xr-x
mariadbd-multi
26.69
KB
-rwxr-xr-x
mariadbd-safe
30.39
KB
-rwxr-xr-x
mariadbd-safe-helper
3.5
MB
-rwxr-xr-x
mcookie
15.44
KB
-rwxr-xr-x
md5sum
40.53
KB
-rwxr-xr-x
mdig
44.2
KB
-rwxr-xr-x
melt
23.82
KB
-rwxr-xr-x
memaslap
65.37
KB
-rwxr-xr-x
memcached
108.77
KB
-rwxr-xr-x
memcached-tool
4.18
KB
-rwxr-xr-x
memcapable
57.71
KB
-rwxr-xr-x
memcat
19.99
KB
-rwxr-xr-x
memcp
20.19
KB
-rwxr-xr-x
memdump
15.81
KB
-rwxr-xr-x
memerror
15.46
KB
-rwxr-xr-x
memexist
15.72
KB
-rwxr-xr-x
memflush
15.73
KB
-rwxr-xr-x
memparse
11.06
KB
-rwxr-xr-x
memping
15.84
KB
-rwxr-xr-x
memrm
15.78
KB
-rwxr-xr-x
memslap
24.55
KB
-rwxr-xr-x
memstat
20.17
KB
-rwxr-xr-x
memtouch
15.8
KB
-rwxr-xr-x
mesg
10.98
KB
-rwxr-xr-x
mimedump
2.29
KB
-rwxr-xr-x
mimeencode
849
B
-rwxr-xr-x
mimeexplode
3.28
KB
-rwxr-xr-x
mimepostcard
2.68
KB
-rwxr-xr-x
mimesend
3.06
KB
-rwxr-xr-x
mixartloader
15.38
KB
-rwxr-xr-x
mkdir
77.9
KB
-rwxr-xr-x
mkfifo
61.59
KB
-rwxr-xr-x
mkfontdir
65
B
-rwxr-xr-x
mkfontscale
32.48
KB
-rwxr-xr-x
mkinitrd
2.94
KB
-rwxr-xr-x
mknod
65.61
KB
-rwxr-xr-x
mktemp
40.66
KB
-rwxr-xr-x
modutil
165.78
KB
-rwxr-xr-x
mogrify
7.04
KB
-rwxr-xr-x
montage
7.04
KB
-rwxr-xr-x
more
40.15
KB
-rwxr-xr-x
mount
43.23
KB
-rwsr-xr-x
mountpoint
15.32
KB
-rwxr-xr-x
mpstat
52.53
KB
-rwxr-xr-x
msgattrib
23.36
KB
-rwxr-xr-x
msgcat
23.34
KB
-rwxr-xr-x
msgcmp
23.59
KB
-rwxr-xr-x
msgcomm
19.31
KB
-rwxr-xr-x
msgconv
19.3
KB
-rwxr-xr-x
msgen
19.3
KB
-rwxr-xr-x
msgexec
15.33
KB
-rwxr-xr-x
msgfilter
27.56
KB
-rwxr-xr-x
msgfmt
77.05
KB
-rwxr-xr-x
msggrep
35.97
KB
-rwxr-xr-x
msghack
12.45
KB
-rwxr-xr-x
msginit
44.38
KB
-rwxr-xr-x
msgmerge
52.6
KB
-rwxr-xr-x
msgunfmt
31.7
KB
-rwxr-xr-x
msguniq
19.31
KB
-rwxr-xr-x
msql2mysql
1.41
KB
-rwxr-xr-x
mv
127.3
KB
-rwxr-xr-x
my_print_defaults
3.53
MB
-rwxr-xr-x
myisam_ftdump
3.89
MB
-rwxr-xr-x
myisamchk
4.01
MB
-rwxr-xr-x
myisamlog
3.87
MB
-rwxr-xr-x
myisampack
3.91
MB
-rwxr-xr-x
mysql
4.33
MB
-rwxr-xr-x
mysql_config
4.58
KB
-rwxr-xr-x
mysql_convert_table_format
4.12
KB
-rwxr-xr-x
mysql_embedded
21.82
MB
-rwxr-xr-x
mysql_find_rows
3.21
KB
-rwxr-xr-x
mysql_fix_extensions
1.22
KB
-rwxr-xr-x
mysql_install_db
21.57
KB
-rwxr-xr-x
mysql_plugin
3.54
MB
-rwxr-xr-x
mysql_secure_installation
13.37
KB
-rwxr-xr-x
mysql_setpermission
17.13
KB
-rwxr-xr-x
mysql_tzinfo_to_sql
3.54
MB
-rwxr-xr-x
mysql_upgrade
3.63
MB
-rwxr-xr-x
mysql_waitpid
3.53
MB
-rwxr-xr-x
mysqlaccess
109.33
KB
-rwxr-xr-x
mysqladmin
3.85
MB
-rwxr-xr-x
mysqlbinlog
4.11
MB
-rwxr-xr-x
mysqlcheck
3.85
MB
-rwxr-xr-x
mysqld_multi
26.69
KB
-rwxr-xr-x
mysqld_safe
30.39
KB
-rwxr-xr-x
mysqld_safe_helper
3.5
MB
-rwxr-xr-x
mysqldump
3.94
MB
-rwxr-xr-x
mysqldumpslow
8.05
KB
-rwxr-xr-x
mysqlhotcopy
34.14
KB
-rwxr-xr-x
mysqlimport
3.84
MB
-rwxr-xr-x
mysqlshow
3.84
MB
-rwxr-xr-x
mysqlslap
3.86
MB
-rwxr-xr-x
nail
383.67
KB
-rwxr-xr-x
named-rrchecker
15.32
KB
-rwxr-xr-x
namei
27.95
KB
-rwxr-xr-x
nano
201.08
KB
-rwxr-xr-x
ncurses5-config
5.68
KB
-rwxr-xr-x
ncursesw5-config
5.68
KB
-rwxr-xr-x
ndptool
23.63
KB
-rwxr-xr-x
needs-restarting
8.17
KB
-rwxr-xr-x
neqn
271
B
-rwxr-xr-x
net-server
3.34
KB
-rwxr-xr-x
net-snmp-config
1.3
KB
-rwxr-xr-x
net-snmp-config-x86_64
29.46
KB
-rwxr-xr-x
net-snmp-create-v3-user
2.98
KB
-rwxr-xr-x
netstat
151.38
KB
-rwxr-xr-x
nettle-hash
11.33
KB
-rwxr-xr-x
nettle-lfib-stream
7.05
KB
-rwxr-xr-x
newaliases
322.16
KB
-rwxr-xr-x
newaliases.postfix
322.16
KB
-rwxr-xr-x
newgidmap
38.09
KB
-rwxr-xr-x
newgrp
40.95
KB
-rwsr-xr-x
newuidmap
38.06
KB
-rwxr-xr-x
nf-ct-add
11.8
KB
-rwxr-xr-x
nf-ct-list
15.84
KB
-rwxr-xr-x
nf-exp-add
16.21
KB
-rwxr-xr-x
nf-exp-delete
15.99
KB
-rwxr-xr-x
nf-exp-list
11.8
KB
-rwxr-xr-x
nf-log
11.23
KB
-rwxr-xr-x
nf-monitor
11.18
KB
-rwxr-xr-x
nf-queue
11.27
KB
-rwxr-xr-x
nfsiostat-sysstat
52.52
KB
-rwxr-xr-x
ngettext
35.95
KB
-rwxr-xr-x
nice
32.32
KB
-rwxr-xr-x
nisdomainname
15.41
KB
-rwxr-xr-x
nl
40.6
KB
-rwxr-xr-x
nl-addr-add
11.65
KB
-rwxr-xr-x
nl-addr-delete
11.73
KB
-rwxr-xr-x
nl-addr-list
15.88
KB
-rwxr-xr-x
nl-class-add
11.7
KB
-rwxr-xr-x
nl-class-delete
11.55
KB
-rwxr-xr-x
nl-class-list
11.48
KB
-rwxr-xr-x
nl-classid-lookup
11.29
KB
-rwxr-xr-x
nl-cls-add
11.76
KB
-rwxr-xr-x
nl-cls-delete
11.68
KB
-rwxr-xr-x
nl-cls-list
11.58
KB
-rwxr-xr-x
nl-fib-lookup
11.41
KB
-rwxr-xr-x
nl-link-enslave
7.05
KB
-rwxr-xr-x
nl-link-ifindex2name
7.06
KB
-rwxr-xr-x
nl-link-list
11.52
KB
-rwxr-xr-x
nl-link-name2ifindex
7.05
KB
-rwxr-xr-x
nl-link-release
7.05
KB
-rwxr-xr-x
nl-link-set
11.64
KB
-rwxr-xr-x
nl-link-stats
11.39
KB
-rwxr-xr-x
nl-list-caches
11.08
KB
-rwxr-xr-x
nl-list-sockets
7.06
KB
-rwxr-xr-x
nl-monitor
11.2
KB
-rwxr-xr-x
nl-neigh-add
11.51
KB
-rwxr-xr-x
nl-neigh-delete
11.57
KB
-rwxr-xr-x
nl-neigh-list
11.46
KB
-rwxr-xr-x
nl-neightbl-list
11.27
KB
-rwxr-xr-x
nl-pktloc-lookup
11.36
KB
-rwxr-xr-x
nl-qdisc-add
11.6
KB
-rwxr-xr-x
nl-qdisc-delete
11.54
KB
-rwxr-xr-x
nl-qdisc-list
11.63
KB
-rwxr-xr-x
nl-route-add
11.73
KB
-rwxr-xr-x
nl-route-delete
15.86
KB
-rwxr-xr-x
nl-route-get
11.18
KB
-rwxr-xr-x
nl-route-list
11.77
KB
-rwxr-xr-x
nl-rule-list
11.3
KB
-rwxr-xr-x
nl-tctree-list
11.53
KB
-rwxr-xr-x
nl-util-addr
7.03
KB
-rwxr-xr-x
nm
45.43
KB
-rwxr-xr-x
nm-online
15.16
KB
-rwxr-xr-x
nmcli
809.16
KB
-rwxr-xr-x
nmtui
646.77
KB
-rwxr-xr-x
nmtui-connect
646.77
KB
-rwxr-xr-x
nmtui-edit
646.77
KB
-rwxr-xr-x
nmtui-hostname
646.77
KB
-rwxr-xr-x
nohup
32.42
KB
-rwxr-xr-x
nomarch
19.6
KB
-rwxr-xr-x
nproc
32.37
KB
-rwxr-xr-x
nroff
3.31
KB
-rwxr-xr-x
nsenter
28.22
KB
-rwxr-xr-x
nslookup
130.93
KB
-rwxr-xr-x
nss-policy-check
15.26
KB
-rwxr-xr-x
nsupdate
65.2
KB
-rwxr-xr-x
numfmt
64.71
KB
-rwxr-xr-x
objcopy
227.34
KB
-rwxr-xr-x
objdump
357.74
KB
-rwxr-xr-x
od
64.81
KB
-rwxr-xr-x
offlineimap
926
B
-rwxr-xr-x
oldfind
186.41
KB
-rwxr-xr-x
onig-config
1.31
KB
-rwxr-xr-x
opax
110.53
KB
-rwxr-xr-x
open
19.48
KB
-rwxr-xr-x
openssl
542.27
KB
-rwxr-xr-x
openvt
19.48
KB
-rwxr-xr-x
os-prober
5.51
KB
-rwxr-xr-x
p11-kit
32.18
KB
-rwxr-xr-x
package-cleanup
17.39
KB
-rwxr-xr-x
package-stash-conflicts
487
B
-rwxr-xr-x
page_owner_sort
10.2
KB
-rwxr-xr-x
pango-list
11.1
KB
-rwxr-xr-x
pango-querymodules-64
28.26
KB
-rwxr-xr-x
pango-view
49.47
KB
-rwxr-xr-x
paperconf
11.23
KB
-rwxr-xr-x
passwd
27.2
KB
-rwsr-xr-x
paste
32.34
KB
-rwxr-xr-x
pathchk
32.31
KB
-rwxr-xr-x
pax
110.53
KB
-rwxr-xr-x
pchrt
3.93
KB
-rwxr-xr-x
pcre-config
2.09
KB
-rwxr-xr-x
perl
11.14
KB
-rwxr-xr-x
perl5.16.3
11.14
KB
-rwxr-xr-x
perlbug
43.61
KB
-rwxr-xr-x
perldoc
203
B
-rwxr-xr-x
perlivp
10.52
KB
-rwxr-xr-x
perlthanks
43.61
KB
-rwxr-xr-x
perror
3.72
MB
-rwxr-xr-x
pflags
2.06
KB
-rwxr-xr-x
pg_config
28
KB
-rwxr-xr-x
pg_dump
305.78
KB
-rwxr-xr-x
pg_dumpall
73.89
KB
-rwxr-xr-x
pg_restore
126.79
KB
-rwxr-xr-x
pgawk
418.63
KB
-rwxr-xr-x
pgrep
27.67
KB
-rwxr-xr-x
php
41.44
MB
-rwxr-xr-x
pic
180.41
KB
-rwxr-xr-x
piconv
7.99
KB
-rwxr-xr-x
pidstat
68.89
KB
-rwxr-xr-x
pinentry
2.54
KB
-rwxr-xr-x
pinentry-curses
49.19
KB
-rwxr-xr-x
ping
64.63
KB
-rwxr-xr-x
ping6
64.63
KB
-rwxr-xr-x
pinky
36.57
KB
-rwxr-xr-x
pip-3
407
B
-rwxr-xr-x
pip-3.6
407
B
-rwxr-xr-x
pip3
407
B
-rwxr-xr-x
pip3.6
407
B
-rwxr-xr-x
pk12util
103.22
KB
-rwxr-xr-x
pkaction
15
KB
-rwxr-xr-x
pkcheck
23.03
KB
-rwxr-xr-x
pkcs1-conv
15.52
KB
-rwxr-xr-x
pkexec
27.02
KB
-rwsr-xr-x
pkg-config
44.38
KB
-rwxr-xr-x
pkgdata
36.7
KB
-rwxr-xr-x
pkill
27.67
KB
-rwxr-xr-x
pkla-admin-identities
19.23
KB
-rwxr-xr-x
pkla-check-authorization
27.3
KB
-rwxr-xr-x
pkttyagent
18.98
KB
-rwxr-xr-x
pl2pm
4.42
KB
-rwxr-xr-x
pldd
14.11
KB
-rwxr-xr-x
plymouth
39.77
KB
-rwxr-xr-x
pmap
27.61
KB
-rwxr-xr-x
pod2html
4
KB
-rwxr-xr-x
pod2man
13.26
KB
-rwxr-xr-x
pod2text
10.75
KB
-rwxr-xr-x
pod2usage
3.67
KB
-rwxr-xr-x
post-grohtml
187.55
KB
-rwxr-xr-x
powernow-k8-decode
6.15
KB
-rwxr-xr-x
pr
65.11
KB
-rwxr-xr-x
pre-grohtml
86.24
KB
-rwxr-xr-x
precat
5.52
KB
-rwxr-xr-x
preconv
40.88
KB
-rwxr-xr-x
preunzip
5.52
KB
-rwxr-xr-x
prezip
5.52
KB
-rwxr-xr-x
prezip-bin
11.05
KB
-rwxr-xr-x
printenv
28.28
KB
-rwxr-xr-x
printf
48.61
KB
-rwxr-xr-x
prlimit
41.2
KB
-rwxr-xr-x
procan
76.99
KB
-rwxr-xr-x
procmail
105.45
KB
-rwxr-xr-x
prove
13.13
KB
-rwxr-xr-x
ps
97.77
KB
-rwxr-xr-x
psed
52.08
KB
-rwxr-xr-x
psfaddtable
19.48
KB
-rwxr-xr-x
psfgettable
19.48
KB
-rwxr-xr-x
psfstriptable
19.48
KB
-rwxr-xr-x
psfxtable
19.48
KB
-rwxr-xr-x
pspell-config
384
B
-rwxr-xr-x
psql
438.74
KB
-rwxr-xr-x
pstruct
35.75
KB
-rwxr-xr-x
pt-align
39.89
KB
-rwxr-xr-x
pt-archiver
255.74
KB
-rwxr-xr-x
pt-config-diff
164.27
KB
-rwxr-xr-x
pt-deadlock-logger
161.56
KB
-rwxr-xr-x
pt-diskstats
160.3
KB
-rwxr-xr-x
pt-duplicate-key-checker
164.22
KB
-rwxr-xr-x
pt-fifo-split
48.11
KB
-rwxr-xr-x
pt-find
145.46
KB
-rwxr-xr-x
pt-fingerprint
64.85
KB
-rwxr-xr-x
pt-fk-error-logger
129.3
KB
-rwxr-xr-x
pt-heartbeat
186.97
KB
-rwxr-xr-x
pt-index-usage
219.97
KB
-rwxr-xr-x
pt-ioprofile
31.59
KB
-rwxr-xr-x
pt-kill
242.13
KB
-rwxr-xr-x
pt-mext
21.24
KB
-rwxr-xr-x
pt-mysql-summary
98.62
KB
-rwxr-xr-x
pt-online-schema-change
374.96
KB
-rwxr-xr-x
pt-pmp
24.02
KB
-rwxr-xr-x
pt-query-digest
505.41
KB
-rwxr-xr-x
pt-show-grants
70.84
KB
-rwxr-xr-x
pt-sift
36.84
KB
-rwxr-xr-x
pt-slave-delay
141.38
KB
-rwxr-xr-x
pt-slave-find
124
KB
-rwxr-xr-x
pt-slave-restart
174.58
KB
-rwxr-xr-x
pt-stalk
67.93
KB
-rwxr-xr-x
pt-summary
87.76
KB
-rwxr-xr-x
pt-table-checksum
408.52
KB
-rwxr-xr-x
pt-table-sync
386.36
KB
-rwxr-xr-x
pt-table-usage
217.38
KB
-rwxr-xr-x
pt-upgrade
322.26
KB
-rwxr-xr-x
pt-variable-advisor
172.12
KB
-rwxr-xr-x
pt-visual-explain
99.27
KB
-rwxr-xr-x
ptar
3.43
KB
-rwxr-xr-x
ptardiff
2.41
KB
-rwxr-xr-x
ptargrep
4.13
KB
-rwxr-xr-x
ptaskset
3.8
KB
-rwxr-xr-x
ptx
65.08
KB
-rwxr-xr-x
pure-ftpwho
6.95
KB
-rwxr-xr-x
pure-pw
39.5
KB
-rwxr-xr-x
pure-pwconvert
10.95
KB
-rwxr-xr-x
pure-statsdecode
10.95
KB
-rwxr-xr-x
pwd
32.45
KB
-rwxr-xr-x
pwdx
11.27
KB
-rwxr-xr-x
pwmake
11.13
KB
-rwxr-xr-x
pwscore
11.13
KB
-rwxr-xr-x
pydoc
78
B
-rwxr-xr-x
pydoc3
78
B
-rwxr-xr-x
pydoc3.6
78
B
-rwxr-xr-x
python
6.98
KB
-rwxr-xr-x
python2
6.98
KB
-rwxr-xr-x
python2.7
6.98
KB
-rwxr-xr-x
python3
11.07
KB
-rwxr-xr-x
python3.6
11.07
KB
-rwxr-xr-x
python3.6m
11.07
KB
-rwxr-xr-x
pyvenv
435
B
-rwxr-xr-x
pyvenv-3.6
435
B
-rwxr-xr-x
qemu-ga
267.8
KB
-rwxr-xr-x
quota
83.31
KB
-rwxr-xr-x
quotasync
70.84
KB
-rwxr-xr-x
ranlib
61.22
KB
-rwxr-xr-x
raw
15.27
KB
-rwxr-xr-x
razor-admin
905
B
-rwxr-xr-x
razor-check
907
B
-rwxr-xr-x
razor-client
624
B
-rwxr-xr-x
razor-report
911
B
-rwxr-xr-x
razor-revoke
911
B
-rwxr-xr-x
read
28
B
-rwxr-xr-x
readelf
509.83
KB
-rwxr-xr-x
readlink
40.82
KB
-rwxr-xr-x
realpath
61.23
KB
-rwxr-xr-x
rearj
60.17
KB
-rwxr-xr-x
recode
39.87
KB
-rwxr-xr-x
recode-sr-latin
15.29
KB
-rwxr-xr-x
redis-benchmark
1.28
MB
-rwxr-xr-x
redis-check-aof
2.79
MB
-rwxr-xr-x
redis-check-rdb
2.79
MB
-rwxr-xr-x
redis-cli
926.3
KB
-rwxr-xr-x
redis-sentinel
2.79
MB
-rwxr-xr-x
redis-server
2.79
MB
-rwxr-xr-x
reindexdb
57.27
KB
-rwxr-xr-x
rename
11.26
KB
-rwxr-xr-x
renice
11.21
KB
-rwxr-xr-x
replace
3.51
MB
-rwxr-xr-x
repo-graph
4.09
KB
-rwxr-xr-x
repo-rss
10.07
KB
-rwxr-xr-x
repoclosure
11.42
KB
-rwxr-xr-x
repodiff
13.97
KB
-rwxr-xr-x
repomanage
6.88
KB
-rwxr-xr-x
repoquery
55.68
KB
-rwxr-xr-x
reposync
14.14
KB
-rwxr-xr-x
repotrack
9.86
KB
-rwxr-xr-x
rescan-scsi-bus.sh
33.81
KB
-rwxr-xr-x
reset
19.6
KB
-rwxr-xr-x
resizecons
19.63
KB
-rwxr-xr-x
resolve_stack_dump
3.53
MB
-rwxr-xr-x
resolveip
3.53
MB
-rwxr-xr-x
rev
11.26
KB
-rwxr-xr-x
rhash
71.57
KB
-rwxr-xr-x
rkhunter
562.04
KB
-rwxr-xr-x
rm
61.4
KB
-rwxr-xr-x
rmail
262
B
-rwxr-xr-x
rmail.postfix
262
B
-rwxr-xr-x
rmdir
44.46
KB
-rwxr-xr-x
rnano
201.08
KB
-rwxr-xr-x
rpcgen
90.45
KB
-rwxr-xr-x
rpm
15.75
KB
-rwxr-xr-x
rpm2cpio
11.16
KB
-rwxr-xr-x
rpmdb
11.73
KB
-rwxr-xr-x
rpmgraph
15.63
KB
-rwxr-xr-x
rpmkeys
11.73
KB
-rwxr-xr-x
rpmquery
15.75
KB
-rwxr-xr-x
rpmverify
15.75
KB
-rwxr-xr-x
rrdcached
52.98
KB
-rwxr-xr-x
rrdcgi
27.81
KB
-rwxr-xr-x
rrdcreate
89.35
KB
-rwxr-xr-x
rrdinfo
89.35
KB
-rwxr-xr-x
rrdtool
27.49
KB
-rwxr-xr-x
rrdupdate
89.35
KB
-rwxr-xr-x
rsync
488.38
KB
-rwxr-xr-x
rsyslog-recover-qi.pl
5.96
KB
-rwxr-xr-x
run-parts
2.04
KB
-rwxr-xr-x
run-with-aspell
85
B
-rwxr-xr-x
runcon
32.47
KB
-rwxr-xr-x
rvi
906.45
KB
-rwxr-xr-x
rview
906.45
KB
-rwxr-xr-x
s2p
52.08
KB
-rwxr-xr-x
sa-awl
4.91
KB
-rwxr-xr-x
sa-check_spamd
14.45
KB
-rwxr-xr-x
sa-compile
21.62
KB
-rwxr-xr-x
sa-learn
44.5
KB
-rwxr-xr-x
sa-update
68.06
KB
-rwxr-xr-x
sadf
168.45
KB
-rwxr-xr-x
sandbox
17.52
KB
-rwxr-xr-x
sar
95.38
KB
-rwxr-xr-x
sasl2-sample-client
19.27
KB
-rwxr-xr-x
sasl2-sample-server
19.1
KB
-rwxr-xr-x
scp
89.23
KB
-rwxr-xr-x
screen
464.1
KB
-rwxr-sr-x
script
19.61
KB
-rwxr-xr-x
scriptreplay
15.29
KB
-rwxr-xr-x
scsi-rescan
33.81
KB
-rwxr-xr-x
scsi_logging_level
7.72
KB
-rwxr-xr-x
scsi_mandat
3.55
KB
-rwxr-xr-x
scsi_readcap
1.3
KB
-rwxr-xr-x
scsi_ready
1.09
KB
-rwxr-xr-x
scsi_satl
3.76
KB
-rwxr-xr-x
scsi_start
1.25
KB
-rwxr-xr-x
scsi_stop
1.44
KB
-rwxr-xr-x
scsi_temperature
936
B
-rwxr-xr-x
sdiff
48.41
KB
-rwxr-xr-x
secon
24.06
KB
-rwxr-xr-x
sed
74.29
KB
-rwxr-xr-x
sedismod
249.42
KB
-rwxr-xr-x
sedispol
176.32
KB
-rwxr-xr-x
semodule_package
15.3
KB
-rwxr-xr-x
seq
48.48
KB
-rwxr-xr-x
sessreg
11.24
KB
-rwxr-xr-x
setarch
15.27
KB
-rwxr-xr-x
setfacl
36.73
KB
-rwxr-xr-x
setfont
40.38
KB
-rwxr-xr-x
setkeycodes
11.16
KB
-rwxr-xr-x
setleds
11.16
KB
-rwxr-xr-x
setmetamode
11.21
KB
-rwxr-xr-x
setpriv
36.06
KB
-rwxr-xr-x
setsid
11.23
KB
-rwxr-xr-x
setterm
27.48
KB
-rwxr-xr-x
setup-nsssysinit
1.5
KB
-rwxr-xr-x
setup-nsssysinit.sh
1.5
KB
-rwxr-xr-x
setvtrgb
11.34
KB
-rwxr-xr-x
sexp-conv
23.63
KB
-rwxr-xr-x
sftp
142.01
KB
-rwxr-xr-x
sfv-hash
71.57
KB
-rwxr-xr-x
sg
40.95
KB
-rwsr-xr-x
sg_compare_and_write
15.82
KB
-rwxr-xr-x
sg_copy_results
20.02
KB
-rwxr-xr-x
sg_dd
43.63
KB
-rwxr-xr-x
sg_decode_sense
15.49
KB
-rwxr-xr-x
sg_emc_trespass
11.09
KB
-rwxr-xr-x
sg_format
24.05
KB
-rwxr-xr-x
sg_get_config
28.22
KB
-rwxr-xr-x
sg_get_lba_status
15.5
KB
-rwxr-xr-x
sg_ident
15.46
KB
-rwxr-xr-x
sg_inq
83.68
KB
-rwxr-xr-x
sg_logs
92.48
KB
-rwxr-xr-x
sg_luns
19.65
KB
-rwxr-xr-x
sg_map
15.23
KB
-rwxr-xr-x
sg_map26
23.77
KB
-rwxr-xr-x
sg_modes
29.3
KB
-rwxr-xr-x
sg_opcodes
23.88
KB
-rwxr-xr-x
sg_persist
28.34
KB
-rwxr-xr-x
sg_prevent
11.29
KB
-rwxr-xr-x
sg_raw
19.78
KB
-rwxr-xr-x
sg_rbuf
19.66
KB
-rwxr-xr-x
sg_rdac
15.13
KB
-rwxr-xr-x
sg_read
23.33
KB
-rwxr-xr-x
sg_read_block_limits
11.31
KB
-rwxr-xr-x
sg_read_buffer
15.53
KB
-rwxr-xr-x
sg_read_long
15.55
KB
-rwxr-xr-x
sg_readcap
19.66
KB
-rwxr-xr-x
sg_reassign
15.54
KB
-rwxr-xr-x
sg_referrals
15.48
KB
-rwxr-xr-x
sg_requests
15.58
KB
-rwxr-xr-x
sg_reset
11.46
KB
-rwxr-xr-x
sg_rmsn
11.32
KB
-rwxr-xr-x
sg_rtpg
15.39
KB
-rwxr-xr-x
sg_safte
19.59
KB
-rwxr-xr-x
sg_sanitize
19.88
KB
-rwxr-xr-x
sg_sat_identify
15.55
KB
-rwxr-xr-x
sg_sat_phy_event
15.87
KB
-rwxr-xr-x
sg_sat_set_features
15.41
KB
-rwxr-xr-x
sg_scan
15.34
KB
-rwxr-xr-x
sg_senddiag
20.02
KB
-rwxr-xr-x
sg_ses
67.55
KB
-rwxr-xr-x
sg_start
19.7
KB
-rwxr-xr-x
sg_stpg
19.61
KB
-rwxr-xr-x
sg_sync
15.52
KB
-rwxr-xr-x
sg_test_rwbuf
15.6
KB
-rwxr-xr-x
sg_turs
15.48
KB
-rwxr-xr-x
sg_unmap
15.5
KB
-rwxr-xr-x
sg_verify
15.7
KB
-rwxr-xr-x
sg_vpd
77.67
KB
-rwxr-xr-x
sg_wr_mode
19.55
KB
-rwxr-xr-x
sg_write_buffer
15.66
KB
-rwxr-xr-x
sg_write_long
15.55
KB
-rwxr-xr-x
sg_write_same
19.9
KB
-rwxr-xr-x
sg_xcopy
35.61
KB
-rwxr-xr-x
sginfo
65.8
KB
-rwxr-xr-x
sgm_dd
31.52
KB
-rwxr-xr-x
sgp_dd
35.83
KB
-rwxr-xr-x
sh
941.93
KB
-rwxr-xr-x
sha1sum
36.57
KB
-rwxr-xr-x
sha224sum
40.63
KB
-rwxr-xr-x
sha256sum
40.63
KB
-rwxr-xr-x
sha384sum
40.65
KB
-rwxr-xr-x
sha512sum
40.65
KB
-rwxr-xr-x
shasum
8.4
KB
-rwxr-xr-x
show-changed-rco
10.13
KB
-rwxr-xr-x
show-installed
16.18
KB
-rwxr-xr-x
showconsolefont
15.47
KB
-rwxr-xr-x
showkey
15.23
KB
-rwxr-xr-x
showrgb
7.08
KB
-rwxr-xr-x
shred
52.94
KB
-rwxr-xr-x
shuf
49.13
KB
-rwxr-xr-x
sieve-dump
23.05
KB
-rwxr-xr-x
sieve-filter
27.06
KB
-rwxr-xr-x
sieve-test
27.13
KB
-rwxr-xr-x
sievec
23.06
KB
-rwxr-xr-x
signver
102.6
KB
-rwxr-xr-x
sigtool
207.89
KB
-rwxr-xr-x
sim_client
14.97
KB
-rwxr-xr-x
size
32.35
KB
-rwxr-xr-x
skill
23.62
KB
-rwxr-xr-x
slabinfo
34.96
KB
-rwxr-xr-x
slabtop
19.52
KB
-rwxr-xr-x
sleep
32.35
KB
-rwxr-xr-x
slogin
756.4
KB
-rwxr-xr-x
snice
23.62
KB
-rwxr-xr-x
snmpconf
25.52
KB
-rwxr-xr-x
socat
380.43
KB
-rwxr-xr-x
soelim
32.59
KB
-rwxr-xr-x
sort
114.94
KB
-rwxr-xr-x
sotruss
4.24
KB
-rwxr-xr-x
spamassassin
29.22
KB
-rwxr-xr-x
spamc
45.19
KB
-rwxr-xr-x
spamd
125.25
KB
-rwxr-xr-x
spell
122
B
-rwxr-xr-x
spfquery
4.49
KB
-rwxr-xr-x
splain
18.03
KB
-rwxr-xr-x
split
69.47
KB
-rwxr-xr-x
sprof
22.1
KB
-rwxr-xr-x
sqlite3
54.95
KB
-rwxr-xr-x
ssh
756.4
KB
-rwxr-xr-x
ssh-add
352.46
KB
-rwxr-xr-x
ssh-agent
373.25
KB
---x--s--x
ssh-copy-id
10.22
KB
-rwxr-xr-x
ssh-keygen
409.38
KB
-rwxr-xr-x
ssh-keyscan
430.7
KB
-rwxr-xr-x
sshpass
15.37
KB
-rwxr-xr-x
ssltap
118.41
KB
-rwxr-xr-x
stat
77.19
KB
-rwxr-xr-x
statist
11.18
KB
-rwxr-xr-x
stdbuf
64.88
KB
-rwxr-xr-x
stream
7.04
KB
-rwxr-xr-x
strings
32.55
KB
-rwxr-xr-x
strip
227.34
KB
-rwxr-xr-x
stty
68.61
KB
-rwxr-xr-x
su
31.38
KB
-rwsr-xr-x
sudo
147.88
KB
---s--x--x
sudoedit
147.88
KB
---s--x--x
sudoreplay
56.11
KB
---x--x--x
sum
36.55
KB
-rwxr-xr-x
svn
232.45
KB
-rwxr-xr-x
svnadmin
67.4
KB
-rwxr-xr-x
svndumpfilter
35.32
KB
-rwxr-xr-x
svnlook
67.45
KB
-rwxr-xr-x
svnrdump
51.54
KB
-rwxr-xr-x
svnserve
83.95
KB
-rwxr-xr-x
svnsync
51.59
KB
-rwxr-xr-x
svnversion
15.15
KB
-rwxr-xr-x
sxpm
19.83
KB
-rwxr-xr-x
sync
28.33
KB
-rwxr-xr-x
systemctl
704.8
KB
-rwxr-xr-x
systemd-analyze
1.49
MB
-rwxr-xr-x
systemd-ask-password
60.38
KB
-rwxr-xr-x
systemd-cat
39.98
KB
-rwxr-xr-x
systemd-cgls
326.29
KB
-rwxr-xr-x
systemd-cgtop
85.09
KB
-rwxr-xr-x
systemd-coredumpctl
154.52
KB
-rwxr-xr-x
systemd-delta
76.84
KB
-rwxr-xr-x
systemd-detect-virt
39.97
KB
-rwxr-xr-x
systemd-escape
48.2
KB
-rwxr-xr-x
systemd-firstboot
101.52
KB
-rwxr-xr-x
systemd-hwdb
85.31
KB
-rwxr-xr-x
systemd-inhibit
309.83
KB
-rwxr-xr-x
systemd-loginctl
489.62
KB
-rwxr-xr-x
systemd-machine-id-setup
52.23
KB
-rwxr-xr-x
systemd-notify
48.13
KB
-rwxr-xr-x
systemd-nspawn
545.88
KB
-rwxr-xr-x
systemd-path
52.16
KB
-rwxr-xr-x
systemd-run
387.05
KB
-rwxr-xr-x
systemd-stdio-bridge
305.77
KB
-rwxr-xr-x
systemd-sysv-convert
3.89
KB
-rwxr-xr-x
systemd-tmpfiles
146.13
KB
-rwxr-xr-x
systemd-tty-ask-password-agent
84.75
KB
-rwxr-xr-x
tabs
15.31
KB
-rwxr-xr-x
tac
32.48
KB
-rwxr-xr-x
tail
65.25
KB
-rwxr-xr-x
tailf
23.88
KB
-rwxr-xr-x
tapestat
52.55
KB
-rwxr-xr-x
tar
338.02
KB
-rwxr-xr-x
taskset
32.22
KB
-rwxr-xr-x
tbl
115.96
KB
-rwxr-xr-x
tcamgr
23.47
KB
-rwxr-xr-x
tcamttest
19.37
KB
-rwxr-xr-x
tcatest
52.78
KB
-rwxr-xr-x
tcbmgr
27.66
KB
-rwxr-xr-x
tcbmttest
47.96
KB
-rwxr-xr-x
tcbtest
64.47
KB
-rwxr-xr-x
tcfmgr
19.5
KB
-rwxr-xr-x
tcfmttest
31.79
KB
-rwxr-xr-x
tcftest
43.86
KB
-rwxr-xr-x
tchmgr
23.52
KB
-rwxr-xr-x
tchmttest
43.87
KB
-rwxr-xr-x
tchtest
52.29
KB
-rwxr-xr-x
tctmgr
31.82
KB
-rwxr-xr-x
tctmttest
39.72
KB
-rwxr-xr-x
tcttest
51.9
KB
-rwxr-xr-x
tcucodec
31.75
KB
-rwxr-xr-x
tcumttest
19.52
KB
-rwxr-xr-x
tcutest
65.61
KB
-rwxr-xr-x
teamd
155.09
KB
-rwxr-xr-x
teamdctl
28.98
KB
-rwxr-xr-x
teamnl
19.09
KB
-rwxr-xr-x
tee
32.38
KB
-rwxr-xr-x
test
36.46
KB
-rwxr-xr-x
testgdbm
29.77
KB
-rwxr-xr-x
tic
64.26
KB
-rwxr-xr-x
tiger-hash
71.57
KB
-rwxr-xr-x
timedatectl
330.22
KB
-rwxr-xr-x
timeout
53.31
KB
-rwxr-xr-x
tload
15.38
KB
-rwxr-xr-x
tmon
31.11
KB
-rwxr-xr-x
toe
15.43
KB
-rwxr-xr-x
top
104.38
KB
-rwxr-xr-x
touch
61.02
KB
-rwxr-xr-x
tput
15.41
KB
-rwxr-xr-x
tr
44.61
KB
-rwxr-xr-x
tracepath
15.05
KB
-rwxr-xr-x
tracepath6
15.05
KB
-rwxr-xr-x
troff
512.96
KB
-rwxr-xr-x
true
28.26
KB
-rwxr-xr-x
truncate
52.68
KB
-rwxr-xr-x
trust
179.08
KB
-rwxr-xr-x
tset
19.6
KB
-rwxr-xr-x
tsort
36.47
KB
-rwxr-xr-x
tth-hash
71.57
KB
-rwxr-xr-x
tty
28.29
KB
-rwxr-xr-x
turbostat
113.15
KB
-rwxr-xr-x
type99
4.57
KB
-rwxr-xr-x
tzselect
7.17
KB
-rwxr-xr-x
uconv
52.57
KB
-rwxr-xr-x
ucs2any
19.47
KB
-rwxr-xr-x
udevadm
414.27
KB
-rwxr-xr-x
ul
19.47
KB
-rwxr-xr-x
umask
29
B
-rwxr-xr-x
umount
31.23
KB
-rwsr-xr-x
unalias
31
B
-rwxr-xr-x
uname
32.3
KB
-rwxr-xr-x
unarj
74
B
-rwxr-xr-x
unexpand
32.45
KB
-rwxr-xr-x
unfreeze
23.82
KB
-rwxr-xr-x
unicode_start
2.5
KB
-rwxr-xr-x
unicode_stop
363
B
-rwxr-xr-x
uniq
44.71
KB
-rwxr-xr-x
unlink
28.3
KB
-rwxr-xr-x
unlz4
105.84
KB
-rwxr-xr-x
unshare
15.45
KB
-rwxr-xr-x
unxz
73.52
KB
-rwxr-xr-x
unzip
185.16
KB
-rwxr-xr-x
unzipsfx
88.66
KB
-rwxr-xr-x
unzoo
27.67
KB
-rwxr-xr-x
update-ca-trust
1.03
KB
-rwxr-xr-x
update-mime-database
52.82
KB
-rwxr-xr-x
updatedb
47.65
KB
-rwxr-xr-x
uptime
11.22
KB
-rwxr-xr-x
urlgrabber
12.17
KB
-rwxr-xr-x
users
32.42
KB
-rwxr-xr-x
usleep
10.95
KB
-rwxr-xr-x
usx2yloader
15.41
KB
-rwxr-xr-x
utmpdump
15.45
KB
-rwxr-xr-x
uuclient
14.97
KB
-rwxr-xr-x
uuidgen
11.21
KB
-rwxr-xr-x
vacuumdb
57.35
KB
-rwxr-xr-x
varnishadm
124.63
KB
-rwxr-xr-x
varnishhist
55.02
KB
-rwxr-xr-x
varnishlog
30.09
KB
-rwxr-xr-x
varnishncsa
98.75
KB
-rwxr-xr-x
varnishstat
88.88
KB
-rwxr-xr-x
varnishtest
1.19
MB
-rwxr-xr-x
varnishtop
61.65
KB
-rwxr-xr-x
vdir
114.85
KB
-rwxr-xr-x
verifytree
10.79
KB
-rwxr-xr-x
vi
906.45
KB
-rwxr-xr-x
view
906.45
KB
-rwxr-xr-x
vlock
15.73
KB
-rwxr-xr-x
vmstat
31.48
KB
-rwxr-xr-x
vxloader
15.38
KB
-rwxr-xr-x
w
19.45
KB
-rwxr-xr-x
wait
28
B
-rwxr-xr-x
wall
14.98
KB
-r-xr-sr-x
watch
24.14
KB
-rwxr-xr-x
watchgnupg
15.35
KB
-rwxr-xr-x
wc
40.67
KB
-rwxr-xr-x
wdctl
40.7
KB
-rwxr-xr-x
wget
408.96
KB
-rwxr-xr-x
whatis
45.49
KB
-rwxr-xr-x
whereis
20.2
KB
-rwxr-xr-x
which
23.77
KB
-rwxr-xr-x
whiptail
27.84
KB
-rwxr-xr-x
whirlpool-hash
71.57
KB
-rwxr-xr-x
who
48.7
KB
-rwxr-xr-x
whoami
28.3
KB
-rwxr-xr-x
word-list-compress
11.03
KB
-rwxr-xr-x
write
19.09
KB
-rwxr-sr-x
wsrep_sst_backup
2.95
KB
-rwxr-xr-x
wsrep_sst_common
54.08
KB
-rw-r--r--
wsrep_sst_mariabackup
50.86
KB
-rwxr-xr-x
wsrep_sst_mysqldump
7.7
KB
-rwxr-xr-x
wsrep_sst_rsync
32.2
KB
-rwxr-xr-x
wsrep_sst_rsync_wan
32.2
KB
-rwxr-xr-x
x86_64
15.27
KB
-rwxr-xr-x
x86_64-redhat-linux-c++
754.59
KB
-rwxr-xr-x
x86_64-redhat-linux-g++
754.59
KB
-rwxr-xr-x
x86_64-redhat-linux-gcc
750.59
KB
-rwxr-xr-x
x86_energy_perf_policy
10.23
KB
-rwxr-xr-x
xargs
60.91
KB
-rwxr-xr-x
xgamma
11.15
KB
-rwxr-xr-x
xgettext
265.35
KB
-rwxr-xr-x
xhost
15.38
KB
-rwxr-xr-x
xinput
52.81
KB
-rwxr-xr-x
xkill
15.27
KB
-rwxr-xr-x
xml2-config
1.68
KB
-rwxr-xr-x
xmlcatalog
15.3
KB
-rwxr-xr-x
xmllint
61.94
KB
-rwxr-xr-x
xmlwf
24
KB
-rwxr-xr-x
xmodmap
32.28
KB
-rwxr-xr-x
xorg-x11-fonts-update-dirs
1.29
KB
-rwxr--r--
xrandr
60.06
KB
-rwxr-xr-x
xrdb
27.78
KB
-rwxr-xr-x
xrefresh
11.16
KB
-rwxr-xr-x
xset
31.72
KB
-rwxr-xr-x
xsetmode
11.1
KB
-rwxr-xr-x
xsetpointer
11.13
KB
-rwxr-xr-x
xsetroot
19.38
KB
-rwxr-xr-x
xslt-config
2.36
KB
-rwxr-xr-x
xsltproc
23.51
KB
-rwxr-xr-x
xstdcmap
15.77
KB
-rwxr-xr-x
xsubpp
4.45
KB
-rwxr-xr-x
xz
73.52
KB
-rwxr-xr-x
xzcat
73.52
KB
-rwxr-xr-x
xzcmp
6.48
KB
-rwxr-xr-x
xzdec
11.21
KB
-rwxr-xr-x
xzdiff
6.48
KB
-rwxr-xr-x
xzegrep
5.76
KB
-rwxr-xr-x
xzfgrep
5.76
KB
-rwxr-xr-x
xzgrep
5.76
KB
-rwxr-xr-x
xzless
1.76
KB
-rwxr-xr-x
xzmore
2.11
KB
-rwxr-xr-x
yes
28.3
KB
-rwxr-xr-x
ypdomainname
15.41
KB
-rwxr-xr-x
yum
801
B
-rwxr-xr-x
yum-builddep
9.95
KB
-rwxr-xr-x
yum-config-manager
9.35
KB
-rwxr-xr-x
yum-debug-dump
8.34
KB
-rwxr-xr-x
yum-debug-restore
7.72
KB
-rwxr-xr-x
yum-groups-manager
10.77
KB
-rwxr-xr-x
yumdownloader
10.85
KB
-rwxr-xr-x
zcat
1.9
KB
-rwxr-xr-x
zcmp
1.72
KB
-rwxr-xr-x
zdiff
5.63
KB
-rwxr-xr-x
zegrep
123
B
-rwxr-xr-x
zfgrep
123
B
-rwxr-xr-x
zforce
2.09
KB
-rwxr-xr-x
zgrep
5.98
KB
-rwxr-xr-x
zip
210.78
KB
-rwxr-xr-x
zipcloak
98.1
KB
-rwxr-xr-x
zipdetails
47.32
KB
-rwxr-xr-x
zipgrep
2.88
KB
-rwxr-xr-x
zipinfo
185.16
KB
-rwxr-xr-x
zipnote
93.73
KB
-rwxr-xr-x
zipsplit
97.75
KB
-rwxr-xr-x
zless
1.99
KB
-rwxr-xr-x
zmore
2.79
KB
-rwxr-xr-x
znew
5.22
KB
-rwxr-xr-x
zsoelim
32.59
KB
-rwxr-xr-x
Delete
Unzip
Zip
${this.title}
Close
Code Editor : pt-find
#!/usr/bin/env perl # This program is part of Percona Toolkit: http://www.percona.com/software/ # See "COPYRIGHT, LICENSE, AND WARRANTY" at the end of this file for legal # notices and disclaimers. use strict; use warnings FATAL => 'all'; # This tool is "fat-packed": most of its dependent modules are embedded # in this file. Setting %INC to this file for each module makes Perl aware # of this so it will not try to load the module from @INC. See the tool's # documentation for a full list of dependencies. BEGIN { $INC{$_} = __FILE__ for map { (my $pkg = "$_.pm") =~ s!::!/!g; $pkg } (qw( Percona::Toolkit DSNParser OptionParser Quoter TableParser Daemon HTTP::Micro VersionCheck )); } # ########################################################################### # Percona::Toolkit package # This package is a copy without comments from the original. The original # with comments and its test file can be found in the Bazaar repository at, # lib/Percona/Toolkit.pm # t/lib/Percona/Toolkit.t # See https://launchpad.net/percona-toolkit for more information. # ########################################################################### { package Percona::Toolkit; our $VERSION = '2.2.15'; use strict; use warnings FATAL => 'all'; use English qw(-no_match_vars); use constant PTDEBUG => $ENV{PTDEBUG} || 0; use Carp qw(carp cluck); use Data::Dumper qw(); require Exporter; our @ISA = qw(Exporter); our @EXPORT_OK = qw( have_required_args Dumper _d ); sub have_required_args { my ($args, @required_args) = @_; my $have_required_args = 1; foreach my $arg ( @required_args ) { if ( !defined $args->{$arg} ) { $have_required_args = 0; carp "Argument $arg is not defined"; } } cluck unless $have_required_args; # print backtrace return $have_required_args; } sub Dumper { local $Data::Dumper::Indent = 1; local $Data::Dumper::Sortkeys = 1; local $Data::Dumper::Quotekeys = 0; Data::Dumper::Dumper(@_); } sub _d { my ($package, undef, $line) = caller 0; @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; } map { defined $_ ? $_ : 'undef' } @_; print STDERR "# $package:$line $PID ", join(' ', @_), "\n"; } 1; } # ########################################################################### # End Percona::Toolkit package # ########################################################################### # ########################################################################### # DSNParser package # This package is a copy without comments from the original. The original # with comments and its test file can be found in the Bazaar repository at, # lib/DSNParser.pm # t/lib/DSNParser.t # See https://launchpad.net/percona-toolkit for more information. # ########################################################################### { package DSNParser; use strict; use warnings FATAL => 'all'; use English qw(-no_match_vars); use constant PTDEBUG => $ENV{PTDEBUG} || 0; use Data::Dumper; $Data::Dumper::Indent = 0; $Data::Dumper::Quotekeys = 0; my $dsn_sep = qr/(?<!\\),/; eval { require DBI; }; my $have_dbi = $EVAL_ERROR ? 0 : 1; sub new { my ( $class, %args ) = @_; foreach my $arg ( qw(opts) ) { die "I need a $arg argument" unless $args{$arg}; } my $self = { opts => {} # h, P, u, etc. Should come from DSN OPTIONS section in POD. }; foreach my $opt ( @{$args{opts}} ) { if ( !$opt->{key} || !$opt->{desc} ) { die "Invalid DSN option: ", Dumper($opt); } PTDEBUG && _d('DSN option:', join(', ', map { "$_=" . (defined $opt->{$_} ? ($opt->{$_} || '') : 'undef') } keys %$opt ) ); $self->{opts}->{$opt->{key}} = { dsn => $opt->{dsn}, desc => $opt->{desc}, copy => $opt->{copy} || 0, }; } return bless $self, $class; } sub prop { my ( $self, $prop, $value ) = @_; if ( @_ > 2 ) { PTDEBUG && _d('Setting', $prop, 'property'); $self->{$prop} = $value; } return $self->{$prop}; } sub parse { my ( $self, $dsn, $prev, $defaults ) = @_; if ( !$dsn ) { PTDEBUG && _d('No DSN to parse'); return; } PTDEBUG && _d('Parsing', $dsn); $prev ||= {}; $defaults ||= {}; my %given_props; my %final_props; my $opts = $self->{opts}; foreach my $dsn_part ( split($dsn_sep, $dsn) ) { $dsn_part =~ s/\\,/,/g; if ( my ($prop_key, $prop_val) = $dsn_part =~ m/^(.)=(.*)$/ ) { $given_props{$prop_key} = $prop_val; } else { PTDEBUG && _d('Interpreting', $dsn_part, 'as h=', $dsn_part); $given_props{h} = $dsn_part; } } foreach my $key ( keys %$opts ) { PTDEBUG && _d('Finding value for', $key); $final_props{$key} = $given_props{$key}; if ( !defined $final_props{$key} && defined $prev->{$key} && $opts->{$key}->{copy} ) { $final_props{$key} = $prev->{$key}; PTDEBUG && _d('Copying value for', $key, 'from previous DSN'); } if ( !defined $final_props{$key} ) { $final_props{$key} = $defaults->{$key}; PTDEBUG && _d('Copying value for', $key, 'from defaults'); } } foreach my $key ( keys %given_props ) { die "Unknown DSN option '$key' in '$dsn'. For more details, " . "please use the --help option, or try 'perldoc $PROGRAM_NAME' " . "for complete documentation." unless exists $opts->{$key}; } if ( (my $required = $self->prop('required')) ) { foreach my $key ( keys %$required ) { die "Missing required DSN option '$key' in '$dsn'. For more details, " . "please use the --help option, or try 'perldoc $PROGRAM_NAME' " . "for complete documentation." unless $final_props{$key}; } } return \%final_props; } sub parse_options { my ( $self, $o ) = @_; die 'I need an OptionParser object' unless ref $o eq 'OptionParser'; my $dsn_string = join(',', map { "$_=".$o->get($_); } grep { $o->has($_) && $o->get($_) } keys %{$self->{opts}} ); PTDEBUG && _d('DSN string made from options:', $dsn_string); return $self->parse($dsn_string); } sub as_string { my ( $self, $dsn, $props ) = @_; return $dsn unless ref $dsn; my @keys = $props ? @$props : sort keys %$dsn; return join(',', map { "$_=" . ($_ eq 'p' ? '...' : $dsn->{$_}) } grep { exists $self->{opts}->{$_} && exists $dsn->{$_} && defined $dsn->{$_} } @keys); } sub usage { my ( $self ) = @_; my $usage = "DSN syntax is key=value[,key=value...] Allowable DSN keys:\n\n" . " KEY COPY MEANING\n" . " === ==== =============================================\n"; my %opts = %{$self->{opts}}; foreach my $key ( sort keys %opts ) { $usage .= " $key " . ($opts{$key}->{copy} ? 'yes ' : 'no ') . ($opts{$key}->{desc} || '[No description]') . "\n"; } $usage .= "\n If the DSN is a bareword, the word is treated as the 'h' key.\n"; return $usage; } sub get_cxn_params { my ( $self, $info ) = @_; my $dsn; my %opts = %{$self->{opts}}; my $driver = $self->prop('dbidriver') || ''; if ( $driver eq 'Pg' ) { $dsn = 'DBI:Pg:dbname=' . ( $info->{D} || '' ) . ';' . join(';', map { "$opts{$_}->{dsn}=$info->{$_}" } grep { defined $info->{$_} } qw(h P)); } else { $dsn = 'DBI:mysql:' . ( $info->{D} || '' ) . ';' . join(';', map { "$opts{$_}->{dsn}=$info->{$_}" } grep { defined $info->{$_} } qw(F h P S A)) . ';mysql_read_default_group=client' . ($info->{L} ? ';mysql_local_infile=1' : ''); } PTDEBUG && _d($dsn); return ($dsn, $info->{u}, $info->{p}); } sub fill_in_dsn { my ( $self, $dbh, $dsn ) = @_; my $vars = $dbh->selectall_hashref('SHOW VARIABLES', 'Variable_name'); my ($user, $db) = $dbh->selectrow_array('SELECT USER(), DATABASE()'); $user =~ s/@.*//; $dsn->{h} ||= $vars->{hostname}->{Value}; $dsn->{S} ||= $vars->{'socket'}->{Value}; $dsn->{P} ||= $vars->{port}->{Value}; $dsn->{u} ||= $user; $dsn->{D} ||= $db; } sub get_dbh { my ( $self, $cxn_string, $user, $pass, $opts ) = @_; $opts ||= {}; my $defaults = { AutoCommit => 0, RaiseError => 1, PrintError => 0, ShowErrorStatement => 1, mysql_enable_utf8 => ($cxn_string =~ m/charset=utf8/i ? 1 : 0), }; @{$defaults}{ keys %$opts } = values %$opts; if (delete $defaults->{L}) { # L for LOAD DATA LOCAL INFILE, our own extension $defaults->{mysql_local_infile} = 1; } if ( $opts->{mysql_use_result} ) { $defaults->{mysql_use_result} = 1; } if ( !$have_dbi ) { die "Cannot connect to MySQL because the Perl DBI module is not " . "installed or not found. Run 'perl -MDBI' to see the directories " . "that Perl searches for DBI. If DBI is not installed, try:\n" . " Debian/Ubuntu apt-get install libdbi-perl\n" . " RHEL/CentOS yum install perl-DBI\n" . " OpenSolaris pkg install pkg:/SUNWpmdbi\n"; } my $dbh; my $tries = 2; while ( !$dbh && $tries-- ) { PTDEBUG && _d($cxn_string, ' ', $user, ' ', $pass, join(', ', map { "$_=>$defaults->{$_}" } keys %$defaults )); $dbh = eval { DBI->connect($cxn_string, $user, $pass, $defaults) }; if ( !$dbh && $EVAL_ERROR ) { if ( $EVAL_ERROR =~ m/locate DBD\/mysql/i ) { die "Cannot connect to MySQL because the Perl DBD::mysql module is " . "not installed or not found. Run 'perl -MDBD::mysql' to see " . "the directories that Perl searches for DBD::mysql. If " . "DBD::mysql is not installed, try:\n" . " Debian/Ubuntu apt-get install libdbd-mysql-perl\n" . " RHEL/CentOS yum install perl-DBD-MySQL\n" . " OpenSolaris pgk install pkg:/SUNWapu13dbd-mysql\n"; } elsif ( $EVAL_ERROR =~ m/not a compiled character set|character set utf8/ ) { PTDEBUG && _d('Going to try again without utf8 support'); delete $defaults->{mysql_enable_utf8}; } if ( !$tries ) { die $EVAL_ERROR; } } } if ( $cxn_string =~ m/mysql/i ) { my $sql; $sql = 'SELECT @@SQL_MODE'; PTDEBUG && _d($dbh, $sql); my ($sql_mode) = eval { $dbh->selectrow_array($sql) }; if ( $EVAL_ERROR ) { die "Error getting the current SQL_MODE: $EVAL_ERROR"; } if ( my ($charset) = $cxn_string =~ m/charset=([\w]+)/ ) { $sql = qq{/*!40101 SET NAMES "$charset"*/}; PTDEBUG && _d($dbh, $sql); eval { $dbh->do($sql) }; if ( $EVAL_ERROR ) { die "Error setting NAMES to $charset: $EVAL_ERROR"; } PTDEBUG && _d('Enabling charset for STDOUT'); if ( $charset eq 'utf8' ) { binmode(STDOUT, ':utf8') or die "Can't binmode(STDOUT, ':utf8'): $OS_ERROR"; } else { binmode(STDOUT) or die "Can't binmode(STDOUT): $OS_ERROR"; } } if ( my $vars = $self->prop('set-vars') ) { $self->set_vars($dbh, $vars); } $sql = 'SET @@SQL_QUOTE_SHOW_CREATE = 1' . '/*!40101, @@SQL_MODE=\'NO_AUTO_VALUE_ON_ZERO' . ($sql_mode ? ",$sql_mode" : '') . '\'*/'; PTDEBUG && _d($dbh, $sql); eval { $dbh->do($sql) }; if ( $EVAL_ERROR ) { die "Error setting SQL_QUOTE_SHOW_CREATE, SQL_MODE" . ($sql_mode ? " and $sql_mode" : '') . ": $EVAL_ERROR"; } } PTDEBUG && _d('DBH info: ', $dbh, Dumper($dbh->selectrow_hashref( 'SELECT DATABASE(), CONNECTION_ID(), VERSION()/*!50038 , @@hostname*/')), 'Connection info:', $dbh->{mysql_hostinfo}, 'Character set info:', Dumper($dbh->selectall_arrayref( "SHOW VARIABLES LIKE 'character_set%'", { Slice => {}})), '$DBD::mysql::VERSION:', $DBD::mysql::VERSION, '$DBI::VERSION:', $DBI::VERSION, ); return $dbh; } sub get_hostname { my ( $self, $dbh ) = @_; if ( my ($host) = ($dbh->{mysql_hostinfo} || '') =~ m/^(\w+) via/ ) { return $host; } my ( $hostname, $one ) = $dbh->selectrow_array( 'SELECT /*!50038 @@hostname, */ 1'); return $hostname; } sub disconnect { my ( $self, $dbh ) = @_; PTDEBUG && $self->print_active_handles($dbh); $dbh->disconnect; } sub print_active_handles { my ( $self, $thing, $level ) = @_; $level ||= 0; printf("# Active %sh: %s %s %s\n", ($thing->{Type} || 'undef'), "\t" x $level, $thing, (($thing->{Type} || '') eq 'st' ? $thing->{Statement} || '' : '')) or die "Cannot print: $OS_ERROR"; foreach my $handle ( grep {defined} @{ $thing->{ChildHandles} } ) { $self->print_active_handles( $handle, $level + 1 ); } } sub copy { my ( $self, $dsn_1, $dsn_2, %args ) = @_; die 'I need a dsn_1 argument' unless $dsn_1; die 'I need a dsn_2 argument' unless $dsn_2; my %new_dsn = map { my $key = $_; my $val; if ( $args{overwrite} ) { $val = defined $dsn_1->{$key} ? $dsn_1->{$key} : $dsn_2->{$key}; } else { $val = defined $dsn_2->{$key} ? $dsn_2->{$key} : $dsn_1->{$key}; } $key => $val; } keys %{$self->{opts}}; return \%new_dsn; } sub set_vars { my ($self, $dbh, $vars) = @_; return unless $vars; foreach my $var ( sort keys %$vars ) { my $val = $vars->{$var}->{val}; (my $quoted_var = $var) =~ s/_/\\_/; my ($var_exists, $current_val); eval { ($var_exists, $current_val) = $dbh->selectrow_array( "SHOW VARIABLES LIKE '$quoted_var'"); }; my $e = $EVAL_ERROR; if ( $e ) { PTDEBUG && _d($e); } if ( $vars->{$var}->{default} && !$var_exists ) { PTDEBUG && _d('Not setting default var', $var, 'because it does not exist'); next; } if ( $current_val && $current_val eq $val ) { PTDEBUG && _d('Not setting var', $var, 'because its value', 'is already', $val); next; } my $sql = "SET SESSION $var=$val"; PTDEBUG && _d($dbh, $sql); eval { $dbh->do($sql) }; if ( my $set_error = $EVAL_ERROR ) { chomp($set_error); $set_error =~ s/ at \S+ line \d+//; my $msg = "Error setting $var: $set_error"; if ( $current_val ) { $msg .= " The current value for $var is $current_val. " . "If the variable is read only (not dynamic), specify " . "--set-vars $var=$current_val to avoid this warning, " . "else manually set the variable and restart MySQL."; } warn $msg . "\n\n"; } } return; } sub _d { my ($package, undef, $line) = caller 0; @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; } map { defined $_ ? $_ : 'undef' } @_; print STDERR "# $package:$line $PID ", join(' ', @_), "\n"; } 1; } # ########################################################################### # End DSNParser package # ########################################################################### # ########################################################################### # OptionParser package # This package is a copy without comments from the original. The original # with comments and its test file can be found in the Bazaar repository at, # lib/OptionParser.pm # t/lib/OptionParser.t # See https://launchpad.net/percona-toolkit for more information. # ########################################################################### { package OptionParser; use strict; use warnings FATAL => 'all'; use English qw(-no_match_vars); use constant PTDEBUG => $ENV{PTDEBUG} || 0; use List::Util qw(max); use Getopt::Long; use Data::Dumper; my $POD_link_re = '[LC]<"?([^">]+)"?>'; sub new { my ( $class, %args ) = @_; my @required_args = qw(); foreach my $arg ( @required_args ) { die "I need a $arg argument" unless $args{$arg}; } my ($program_name) = $PROGRAM_NAME =~ m/([.A-Za-z-]+)$/; $program_name ||= $PROGRAM_NAME; my $home = $ENV{HOME} || $ENV{HOMEPATH} || $ENV{USERPROFILE} || '.'; my %attributes = ( 'type' => 1, 'short form' => 1, 'group' => 1, 'default' => 1, 'cumulative' => 1, 'negatable' => 1, ); my $self = { head1 => 'OPTIONS', # These args are used internally skip_rules => 0, # to instantiate another Option- item => '--(.*)', # Parser obj that parses the attributes => \%attributes, # DSN OPTIONS section. Tools parse_attributes => \&_parse_attribs, # don't tinker with these args. %args, strict => 1, # disabled by a special rule program_name => $program_name, opts => {}, got_opts => 0, short_opts => {}, defaults => {}, groups => {}, allowed_groups => {}, errors => [], rules => [], # desc of rules for --help mutex => [], # rule: opts are mutually exclusive atleast1 => [], # rule: at least one opt is required disables => {}, # rule: opt disables other opts defaults_to => {}, # rule: opt defaults to value of other opt DSNParser => undef, default_files => [ "/etc/percona-toolkit/percona-toolkit.conf", "/etc/percona-toolkit/$program_name.conf", "$home/.percona-toolkit.conf", "$home/.$program_name.conf", ], types => { string => 's', # standard Getopt type int => 'i', # standard Getopt type float => 'f', # standard Getopt type Hash => 'H', # hash, formed from a comma-separated list hash => 'h', # hash as above, but only if a value is given Array => 'A', # array, similar to Hash array => 'a', # array, similar to hash DSN => 'd', # DSN size => 'z', # size with kMG suffix (powers of 2^10) time => 'm', # time, with an optional suffix of s/h/m/d }, }; return bless $self, $class; } sub get_specs { my ( $self, $file ) = @_; $file ||= $self->{file} || __FILE__; my @specs = $self->_pod_to_specs($file); $self->_parse_specs(@specs); open my $fh, "<", $file or die "Cannot open $file: $OS_ERROR"; my $contents = do { local $/ = undef; <$fh> }; close $fh; if ( $contents =~ m/^=head1 DSN OPTIONS/m ) { PTDEBUG && _d('Parsing DSN OPTIONS'); my $dsn_attribs = { dsn => 1, copy => 1, }; my $parse_dsn_attribs = sub { my ( $self, $option, $attribs ) = @_; map { my $val = $attribs->{$_}; if ( $val ) { $val = $val eq 'yes' ? 1 : $val eq 'no' ? 0 : $val; $attribs->{$_} = $val; } } keys %$attribs; return { key => $option, %$attribs, }; }; my $dsn_o = new OptionParser( description => 'DSN OPTIONS', head1 => 'DSN OPTIONS', dsn => 0, # XXX don't infinitely recurse! item => '\* (.)', # key opts are a single character skip_rules => 1, # no rules before opts attributes => $dsn_attribs, parse_attributes => $parse_dsn_attribs, ); my @dsn_opts = map { my $opts = { key => $_->{spec}->{key}, dsn => $_->{spec}->{dsn}, copy => $_->{spec}->{copy}, desc => $_->{desc}, }; $opts; } $dsn_o->_pod_to_specs($file); $self->{DSNParser} = DSNParser->new(opts => \@dsn_opts); } if ( $contents =~ m/^=head1 VERSION\n\n^(.+)$/m ) { $self->{version} = $1; PTDEBUG && _d($self->{version}); } return; } sub DSNParser { my ( $self ) = @_; return $self->{DSNParser}; }; sub get_defaults_files { my ( $self ) = @_; return @{$self->{default_files}}; } sub _pod_to_specs { my ( $self, $file ) = @_; $file ||= $self->{file} || __FILE__; open my $fh, '<', $file or die "Cannot open $file: $OS_ERROR"; my @specs = (); my @rules = (); my $para; local $INPUT_RECORD_SEPARATOR = ''; while ( $para = <$fh> ) { next unless $para =~ m/^=head1 $self->{head1}/; last; } while ( $para = <$fh> ) { last if $para =~ m/^=over/; next if $self->{skip_rules}; chomp $para; $para =~ s/\s+/ /g; $para =~ s/$POD_link_re/$1/go; PTDEBUG && _d('Option rule:', $para); push @rules, $para; } die "POD has no $self->{head1} section" unless $para; do { if ( my ($option) = $para =~ m/^=item $self->{item}/ ) { chomp $para; PTDEBUG && _d($para); my %attribs; $para = <$fh>; # read next paragraph, possibly attributes if ( $para =~ m/: / ) { # attributes $para =~ s/\s+\Z//g; %attribs = map { my ( $attrib, $val) = split(/: /, $_); die "Unrecognized attribute for --$option: $attrib" unless $self->{attributes}->{$attrib}; ($attrib, $val); } split(/; /, $para); if ( $attribs{'short form'} ) { $attribs{'short form'} =~ s/-//; } $para = <$fh>; # read next paragraph, probably short help desc } else { PTDEBUG && _d('Option has no attributes'); } $para =~ s/\s+\Z//g; $para =~ s/\s+/ /g; $para =~ s/$POD_link_re/$1/go; $para =~ s/\.(?:\n.*| [A-Z].*|\Z)//s; PTDEBUG && _d('Short help:', $para); die "No description after option spec $option" if $para =~ m/^=item/; if ( my ($base_option) = $option =~ m/^\[no\](.*)/ ) { $option = $base_option; $attribs{'negatable'} = 1; } push @specs, { spec => $self->{parse_attributes}->($self, $option, \%attribs), desc => $para . (defined $attribs{default} ? " (default $attribs{default})" : ''), group => ($attribs{'group'} ? $attribs{'group'} : 'default'), }; } while ( $para = <$fh> ) { last unless $para; if ( $para =~ m/^=head1/ ) { $para = undef; # Can't 'last' out of a do {} block. last; } last if $para =~ m/^=item /; } } while ( $para ); die "No valid specs in $self->{head1}" unless @specs; close $fh; return @specs, @rules; } sub _parse_specs { my ( $self, @specs ) = @_; my %disables; # special rule that requires deferred checking foreach my $opt ( @specs ) { if ( ref $opt ) { # It's an option spec, not a rule. PTDEBUG && _d('Parsing opt spec:', map { ($_, '=>', $opt->{$_}) } keys %$opt); my ( $long, $short ) = $opt->{spec} =~ m/^([\w-]+)(?:\|([^!+=]*))?/; if ( !$long ) { die "Cannot parse long option from spec $opt->{spec}"; } $opt->{long} = $long; die "Duplicate long option --$long" if exists $self->{opts}->{$long}; $self->{opts}->{$long} = $opt; if ( length $long == 1 ) { PTDEBUG && _d('Long opt', $long, 'looks like short opt'); $self->{short_opts}->{$long} = $long; } if ( $short ) { die "Duplicate short option -$short" if exists $self->{short_opts}->{$short}; $self->{short_opts}->{$short} = $long; $opt->{short} = $short; } else { $opt->{short} = undef; } $opt->{is_negatable} = $opt->{spec} =~ m/!/ ? 1 : 0; $opt->{is_cumulative} = $opt->{spec} =~ m/\+/ ? 1 : 0; $opt->{is_required} = $opt->{desc} =~ m/required/ ? 1 : 0; $opt->{group} ||= 'default'; $self->{groups}->{ $opt->{group} }->{$long} = 1; $opt->{value} = undef; $opt->{got} = 0; my ( $type ) = $opt->{spec} =~ m/=(.)/; $opt->{type} = $type; PTDEBUG && _d($long, 'type:', $type); $opt->{spec} =~ s/=./=s/ if ( $type && $type =~ m/[HhAadzm]/ ); if ( (my ($def) = $opt->{desc} =~ m/default\b(?: ([^)]+))?/) ) { $self->{defaults}->{$long} = defined $def ? $def : 1; PTDEBUG && _d($long, 'default:', $def); } if ( $long eq 'config' ) { $self->{defaults}->{$long} = join(',', $self->get_defaults_files()); } if ( (my ($dis) = $opt->{desc} =~ m/(disables .*)/) ) { $disables{$long} = $dis; PTDEBUG && _d('Deferring check of disables rule for', $opt, $dis); } $self->{opts}->{$long} = $opt; } else { # It's an option rule, not a spec. PTDEBUG && _d('Parsing rule:', $opt); push @{$self->{rules}}, $opt; my @participants = $self->_get_participants($opt); my $rule_ok = 0; if ( $opt =~ m/mutually exclusive|one and only one/ ) { $rule_ok = 1; push @{$self->{mutex}}, \@participants; PTDEBUG && _d(@participants, 'are mutually exclusive'); } if ( $opt =~ m/at least one|one and only one/ ) { $rule_ok = 1; push @{$self->{atleast1}}, \@participants; PTDEBUG && _d(@participants, 'require at least one'); } if ( $opt =~ m/default to/ ) { $rule_ok = 1; $self->{defaults_to}->{$participants[0]} = $participants[1]; PTDEBUG && _d($participants[0], 'defaults to', $participants[1]); } if ( $opt =~ m/restricted to option groups/ ) { $rule_ok = 1; my ($groups) = $opt =~ m/groups ([\w\s\,]+)/; my @groups = split(',', $groups); %{$self->{allowed_groups}->{$participants[0]}} = map { s/\s+//; $_ => 1; } @groups; } if( $opt =~ m/accepts additional command-line arguments/ ) { $rule_ok = 1; $self->{strict} = 0; PTDEBUG && _d("Strict mode disabled by rule"); } die "Unrecognized option rule: $opt" unless $rule_ok; } } foreach my $long ( keys %disables ) { my @participants = $self->_get_participants($disables{$long}); $self->{disables}->{$long} = \@participants; PTDEBUG && _d('Option', $long, 'disables', @participants); } return; } sub _get_participants { my ( $self, $str ) = @_; my @participants; foreach my $long ( $str =~ m/--(?:\[no\])?([\w-]+)/g ) { die "Option --$long does not exist while processing rule $str" unless exists $self->{opts}->{$long}; push @participants, $long; } PTDEBUG && _d('Participants for', $str, ':', @participants); return @participants; } sub opts { my ( $self ) = @_; my %opts = %{$self->{opts}}; return %opts; } sub short_opts { my ( $self ) = @_; my %short_opts = %{$self->{short_opts}}; return %short_opts; } sub set_defaults { my ( $self, %defaults ) = @_; $self->{defaults} = {}; foreach my $long ( keys %defaults ) { die "Cannot set default for nonexistent option $long" unless exists $self->{opts}->{$long}; $self->{defaults}->{$long} = $defaults{$long}; PTDEBUG && _d('Default val for', $long, ':', $defaults{$long}); } return; } sub get_defaults { my ( $self ) = @_; return $self->{defaults}; } sub get_groups { my ( $self ) = @_; return $self->{groups}; } sub _set_option { my ( $self, $opt, $val ) = @_; my $long = exists $self->{opts}->{$opt} ? $opt : exists $self->{short_opts}->{$opt} ? $self->{short_opts}->{$opt} : die "Getopt::Long gave a nonexistent option: $opt"; $opt = $self->{opts}->{$long}; if ( $opt->{is_cumulative} ) { $opt->{value}++; } elsif ( ($opt->{type} || '') eq 's' && $val =~ m/^--?(.+)/ ) { my $next_opt = $1; if ( exists $self->{opts}->{$next_opt} || exists $self->{short_opts}->{$next_opt} ) { $self->save_error("--$long requires a string value"); return; } else { $opt->{value} = $val; } } else { $opt->{value} = $val; } $opt->{got} = 1; PTDEBUG && _d('Got option', $long, '=', $val); } sub get_opts { my ( $self ) = @_; foreach my $long ( keys %{$self->{opts}} ) { $self->{opts}->{$long}->{got} = 0; $self->{opts}->{$long}->{value} = exists $self->{defaults}->{$long} ? $self->{defaults}->{$long} : $self->{opts}->{$long}->{is_cumulative} ? 0 : undef; } $self->{got_opts} = 0; $self->{errors} = []; if ( @ARGV && $ARGV[0] eq "--config" ) { shift @ARGV; $self->_set_option('config', shift @ARGV); } if ( $self->has('config') ) { my @extra_args; foreach my $filename ( split(',', $self->get('config')) ) { eval { push @extra_args, $self->_read_config_file($filename); }; if ( $EVAL_ERROR ) { if ( $self->got('config') ) { die $EVAL_ERROR; } elsif ( PTDEBUG ) { _d($EVAL_ERROR); } } } unshift @ARGV, @extra_args; } Getopt::Long::Configure('no_ignore_case', 'bundling'); GetOptions( map { $_->{spec} => sub { $self->_set_option(@_); } } grep { $_->{long} ne 'config' } # --config is handled specially above. values %{$self->{opts}} ) or $self->save_error('Error parsing options'); if ( exists $self->{opts}->{version} && $self->{opts}->{version}->{got} ) { if ( $self->{version} ) { print $self->{version}, "\n"; } else { print "Error parsing version. See the VERSION section of the tool's documentation.\n"; } exit 1; } if ( @ARGV && $self->{strict} ) { $self->save_error("Unrecognized command-line options @ARGV"); } foreach my $mutex ( @{$self->{mutex}} ) { my @set = grep { $self->{opts}->{$_}->{got} } @$mutex; if ( @set > 1 ) { my $err = join(', ', map { "--$self->{opts}->{$_}->{long}" } @{$mutex}[ 0 .. scalar(@$mutex) - 2] ) . ' and --'.$self->{opts}->{$mutex->[-1]}->{long} . ' are mutually exclusive.'; $self->save_error($err); } } foreach my $required ( @{$self->{atleast1}} ) { my @set = grep { $self->{opts}->{$_}->{got} } @$required; if ( @set == 0 ) { my $err = join(', ', map { "--$self->{opts}->{$_}->{long}" } @{$required}[ 0 .. scalar(@$required) - 2] ) .' or --'.$self->{opts}->{$required->[-1]}->{long}; $self->save_error("Specify at least one of $err"); } } $self->_check_opts( keys %{$self->{opts}} ); $self->{got_opts} = 1; return; } sub _check_opts { my ( $self, @long ) = @_; my $long_last = scalar @long; while ( @long ) { foreach my $i ( 0..$#long ) { my $long = $long[$i]; next unless $long; my $opt = $self->{opts}->{$long}; if ( $opt->{got} ) { if ( exists $self->{disables}->{$long} ) { my @disable_opts = @{$self->{disables}->{$long}}; map { $self->{opts}->{$_}->{value} = undef; } @disable_opts; PTDEBUG && _d('Unset options', @disable_opts, 'because', $long,'disables them'); } if ( exists $self->{allowed_groups}->{$long} ) { my @restricted_groups = grep { !exists $self->{allowed_groups}->{$long}->{$_} } keys %{$self->{groups}}; my @restricted_opts; foreach my $restricted_group ( @restricted_groups ) { RESTRICTED_OPT: foreach my $restricted_opt ( keys %{$self->{groups}->{$restricted_group}} ) { next RESTRICTED_OPT if $restricted_opt eq $long; push @restricted_opts, $restricted_opt if $self->{opts}->{$restricted_opt}->{got}; } } if ( @restricted_opts ) { my $err; if ( @restricted_opts == 1 ) { $err = "--$restricted_opts[0]"; } else { $err = join(', ', map { "--$self->{opts}->{$_}->{long}" } grep { $_ } @restricted_opts[0..scalar(@restricted_opts) - 2] ) . ' or --'.$self->{opts}->{$restricted_opts[-1]}->{long}; } $self->save_error("--$long is not allowed with $err"); } } } elsif ( $opt->{is_required} ) { $self->save_error("Required option --$long must be specified"); } $self->_validate_type($opt); if ( $opt->{parsed} ) { delete $long[$i]; } else { PTDEBUG && _d('Temporarily failed to parse', $long); } } die "Failed to parse options, possibly due to circular dependencies" if @long == $long_last; $long_last = @long; } return; } sub _validate_type { my ( $self, $opt ) = @_; return unless $opt; if ( !$opt->{type} ) { $opt->{parsed} = 1; return; } my $val = $opt->{value}; if ( $val && $opt->{type} eq 'm' ) { # type time PTDEBUG && _d('Parsing option', $opt->{long}, 'as a time value'); my ( $prefix, $num, $suffix ) = $val =~ m/([+-]?)(\d+)([a-z])?$/; if ( !$suffix ) { my ( $s ) = $opt->{desc} =~ m/\(suffix (.)\)/; $suffix = $s || 's'; PTDEBUG && _d('No suffix given; using', $suffix, 'for', $opt->{long}, '(value:', $val, ')'); } if ( $suffix =~ m/[smhd]/ ) { $val = $suffix eq 's' ? $num # Seconds : $suffix eq 'm' ? $num * 60 # Minutes : $suffix eq 'h' ? $num * 3600 # Hours : $num * 86400; # Days $opt->{value} = ($prefix || '') . $val; PTDEBUG && _d('Setting option', $opt->{long}, 'to', $val); } else { $self->save_error("Invalid time suffix for --$opt->{long}"); } } elsif ( $val && $opt->{type} eq 'd' ) { # type DSN PTDEBUG && _d('Parsing option', $opt->{long}, 'as a DSN'); my $prev = {}; my $from_key = $self->{defaults_to}->{ $opt->{long} }; if ( $from_key ) { PTDEBUG && _d($opt->{long}, 'DSN copies from', $from_key, 'DSN'); if ( $self->{opts}->{$from_key}->{parsed} ) { $prev = $self->{opts}->{$from_key}->{value}; } else { PTDEBUG && _d('Cannot parse', $opt->{long}, 'until', $from_key, 'parsed'); return; } } my $defaults = $self->{DSNParser}->parse_options($self); $opt->{value} = $self->{DSNParser}->parse($val, $prev, $defaults); } elsif ( $val && $opt->{type} eq 'z' ) { # type size PTDEBUG && _d('Parsing option', $opt->{long}, 'as a size value'); $self->_parse_size($opt, $val); } elsif ( $opt->{type} eq 'H' || (defined $val && $opt->{type} eq 'h') ) { $opt->{value} = { map { $_ => 1 } split(/(?<!\\),\s*/, ($val || '')) }; } elsif ( $opt->{type} eq 'A' || (defined $val && $opt->{type} eq 'a') ) { $opt->{value} = [ split(/(?<!\\),\s*/, ($val || '')) ]; } else { PTDEBUG && _d('Nothing to validate for option', $opt->{long}, 'type', $opt->{type}, 'value', $val); } $opt->{parsed} = 1; return; } sub get { my ( $self, $opt ) = @_; my $long = (length $opt == 1 ? $self->{short_opts}->{$opt} : $opt); die "Option $opt does not exist" unless $long && exists $self->{opts}->{$long}; return $self->{opts}->{$long}->{value}; } sub got { my ( $self, $opt ) = @_; my $long = (length $opt == 1 ? $self->{short_opts}->{$opt} : $opt); die "Option $opt does not exist" unless $long && exists $self->{opts}->{$long}; return $self->{opts}->{$long}->{got}; } sub has { my ( $self, $opt ) = @_; my $long = (length $opt == 1 ? $self->{short_opts}->{$opt} : $opt); return defined $long ? exists $self->{opts}->{$long} : 0; } sub set { my ( $self, $opt, $val ) = @_; my $long = (length $opt == 1 ? $self->{short_opts}->{$opt} : $opt); die "Option $opt does not exist" unless $long && exists $self->{opts}->{$long}; $self->{opts}->{$long}->{value} = $val; return; } sub save_error { my ( $self, $error ) = @_; push @{$self->{errors}}, $error; return; } sub errors { my ( $self ) = @_; return $self->{errors}; } sub usage { my ( $self ) = @_; warn "No usage string is set" unless $self->{usage}; # XXX return "Usage: " . ($self->{usage} || '') . "\n"; } sub descr { my ( $self ) = @_; warn "No description string is set" unless $self->{description}; # XXX my $descr = ($self->{description} || $self->{program_name} || '') . " For more details, please use the --help option, " . "or try 'perldoc $PROGRAM_NAME' " . "for complete documentation."; $descr = join("\n", $descr =~ m/(.{0,80})(?:\s+|$)/g) unless $ENV{DONT_BREAK_LINES}; $descr =~ s/ +$//mg; return $descr; } sub usage_or_errors { my ( $self, $file, $return ) = @_; $file ||= $self->{file} || __FILE__; if ( !$self->{description} || !$self->{usage} ) { PTDEBUG && _d("Getting description and usage from SYNOPSIS in", $file); my %synop = $self->_parse_synopsis($file); $self->{description} ||= $synop{description}; $self->{usage} ||= $synop{usage}; PTDEBUG && _d("Description:", $self->{description}, "\nUsage:", $self->{usage}); } if ( $self->{opts}->{help}->{got} ) { print $self->print_usage() or die "Cannot print usage: $OS_ERROR"; exit 0 unless $return; } elsif ( scalar @{$self->{errors}} ) { print $self->print_errors() or die "Cannot print errors: $OS_ERROR"; exit 1 unless $return; } return; } sub print_errors { my ( $self ) = @_; my $usage = $self->usage() . "\n"; if ( (my @errors = @{$self->{errors}}) ) { $usage .= join("\n * ", 'Errors in command-line arguments:', @errors) . "\n"; } return $usage . "\n" . $self->descr(); } sub print_usage { my ( $self ) = @_; die "Run get_opts() before print_usage()" unless $self->{got_opts}; my @opts = values %{$self->{opts}}; my $maxl = max( map { length($_->{long}) # option long name + ($_->{is_negatable} ? 4 : 0) # "[no]" if opt is negatable + ($_->{type} ? 2 : 0) # "=x" where x is the opt type } @opts); my $maxs = max(0, map { length($_) + ($self->{opts}->{$_}->{is_negatable} ? 4 : 0) + ($self->{opts}->{$_}->{type} ? 2 : 0) } values %{$self->{short_opts}}); my $lcol = max($maxl, ($maxs + 3)); my $rcol = 80 - $lcol - 6; my $rpad = ' ' x ( 80 - $rcol ); $maxs = max($lcol - 3, $maxs); my $usage = $self->descr() . "\n" . $self->usage(); my @groups = reverse sort grep { $_ ne 'default'; } keys %{$self->{groups}}; push @groups, 'default'; foreach my $group ( reverse @groups ) { $usage .= "\n".($group eq 'default' ? 'Options' : $group).":\n\n"; foreach my $opt ( sort { $a->{long} cmp $b->{long} } grep { $_->{group} eq $group } @opts ) { my $long = $opt->{is_negatable} ? "[no]$opt->{long}" : $opt->{long}; my $short = $opt->{short}; my $desc = $opt->{desc}; $long .= $opt->{type} ? "=$opt->{type}" : ""; if ( $opt->{type} && $opt->{type} eq 'm' ) { my ($s) = $desc =~ m/\(suffix (.)\)/; $s ||= 's'; $desc =~ s/\s+\(suffix .\)//; $desc .= ". Optional suffix s=seconds, m=minutes, h=hours, " . "d=days; if no suffix, $s is used."; } $desc = join("\n$rpad", grep { $_ } $desc =~ m/(.{0,$rcol}(?!\W))(?:\s+|(?<=\W)|$)/g); $desc =~ s/ +$//mg; if ( $short ) { $usage .= sprintf(" --%-${maxs}s -%s %s\n", $long, $short, $desc); } else { $usage .= sprintf(" --%-${lcol}s %s\n", $long, $desc); } } } $usage .= "\nOption types: s=string, i=integer, f=float, h/H/a/A=comma-separated list, d=DSN, z=size, m=time\n"; if ( (my @rules = @{$self->{rules}}) ) { $usage .= "\nRules:\n\n"; $usage .= join("\n", map { " $_" } @rules) . "\n"; } if ( $self->{DSNParser} ) { $usage .= "\n" . $self->{DSNParser}->usage(); } $usage .= "\nOptions and values after processing arguments:\n\n"; foreach my $opt ( sort { $a->{long} cmp $b->{long} } @opts ) { my $val = $opt->{value}; my $type = $opt->{type} || ''; my $bool = $opt->{spec} =~ m/^[\w-]+(?:\|[\w-])?!?$/; $val = $bool ? ( $val ? 'TRUE' : 'FALSE' ) : !defined $val ? '(No value)' : $type eq 'd' ? $self->{DSNParser}->as_string($val) : $type =~ m/H|h/ ? join(',', sort keys %$val) : $type =~ m/A|a/ ? join(',', @$val) : $val; $usage .= sprintf(" --%-${lcol}s %s\n", $opt->{long}, $val); } return $usage; } sub prompt_noecho { shift @_ if ref $_[0] eq __PACKAGE__; my ( $prompt ) = @_; local $OUTPUT_AUTOFLUSH = 1; print STDERR $prompt or die "Cannot print: $OS_ERROR"; my $response; eval { require Term::ReadKey; Term::ReadKey::ReadMode('noecho'); chomp($response = <STDIN>); Term::ReadKey::ReadMode('normal'); print "\n" or die "Cannot print: $OS_ERROR"; }; if ( $EVAL_ERROR ) { die "Cannot read response; is Term::ReadKey installed? $EVAL_ERROR"; } return $response; } sub _read_config_file { my ( $self, $filename ) = @_; open my $fh, "<", $filename or die "Cannot open $filename: $OS_ERROR\n"; my @args; my $prefix = '--'; my $parse = 1; LINE: while ( my $line = <$fh> ) { chomp $line; next LINE if $line =~ m/^\s*(?:\#|\;|$)/; $line =~ s/\s+#.*$//g; $line =~ s/^\s+|\s+$//g; if ( $line eq '--' ) { $prefix = ''; $parse = 0; next LINE; } if ( $parse && (my($opt, $arg) = $line =~ m/^\s*([^=\s]+?)(?:\s*=\s*(.*?)\s*)?$/) ) { push @args, grep { defined $_ } ("$prefix$opt", $arg); } elsif ( $line =~ m/./ ) { push @args, $line; } else { die "Syntax error in file $filename at line $INPUT_LINE_NUMBER"; } } close $fh; return @args; } sub read_para_after { my ( $self, $file, $regex ) = @_; open my $fh, "<", $file or die "Can't open $file: $OS_ERROR"; local $INPUT_RECORD_SEPARATOR = ''; my $para; while ( $para = <$fh> ) { next unless $para =~ m/^=pod$/m; last; } while ( $para = <$fh> ) { next unless $para =~ m/$regex/; last; } $para = <$fh>; chomp($para); close $fh or die "Can't close $file: $OS_ERROR"; return $para; } sub clone { my ( $self ) = @_; my %clone = map { my $hashref = $self->{$_}; my $val_copy = {}; foreach my $key ( keys %$hashref ) { my $ref = ref $hashref->{$key}; $val_copy->{$key} = !$ref ? $hashref->{$key} : $ref eq 'HASH' ? { %{$hashref->{$key}} } : $ref eq 'ARRAY' ? [ @{$hashref->{$key}} ] : $hashref->{$key}; } $_ => $val_copy; } qw(opts short_opts defaults); foreach my $scalar ( qw(got_opts) ) { $clone{$scalar} = $self->{$scalar}; } return bless \%clone; } sub _parse_size { my ( $self, $opt, $val ) = @_; if ( lc($val || '') eq 'null' ) { PTDEBUG && _d('NULL size for', $opt->{long}); $opt->{value} = 'null'; return; } my %factor_for = (k => 1_024, M => 1_048_576, G => 1_073_741_824); my ($pre, $num, $factor) = $val =~ m/^([+-])?(\d+)([kMG])?$/; if ( defined $num ) { if ( $factor ) { $num *= $factor_for{$factor}; PTDEBUG && _d('Setting option', $opt->{y}, 'to num', $num, '* factor', $factor); } $opt->{value} = ($pre || '') . $num; } else { $self->save_error("Invalid size for --$opt->{long}: $val"); } return; } sub _parse_attribs { my ( $self, $option, $attribs ) = @_; my $types = $self->{types}; return $option . ($attribs->{'short form'} ? '|' . $attribs->{'short form'} : '' ) . ($attribs->{'negatable'} ? '!' : '' ) . ($attribs->{'cumulative'} ? '+' : '' ) . ($attribs->{'type'} ? '=' . $types->{$attribs->{type}} : '' ); } sub _parse_synopsis { my ( $self, $file ) = @_; $file ||= $self->{file} || __FILE__; PTDEBUG && _d("Parsing SYNOPSIS in", $file); local $INPUT_RECORD_SEPARATOR = ''; # read paragraphs open my $fh, "<", $file or die "Cannot open $file: $OS_ERROR"; my $para; 1 while defined($para = <$fh>) && $para !~ m/^=head1 SYNOPSIS/; die "$file does not contain a SYNOPSIS section" unless $para; my @synop; for ( 1..2 ) { # 1 for the usage, 2 for the description my $para = <$fh>; push @synop, $para; } close $fh; PTDEBUG && _d("Raw SYNOPSIS text:", @synop); my ($usage, $desc) = @synop; die "The SYNOPSIS section in $file is not formatted properly" unless $usage && $desc; $usage =~ s/^\s*Usage:\s+(.+)/$1/; chomp $usage; $desc =~ s/\n/ /g; $desc =~ s/\s{2,}/ /g; $desc =~ s/\. ([A-Z][a-z])/. $1/g; $desc =~ s/\s+$//; return ( description => $desc, usage => $usage, ); }; sub set_vars { my ($self, $file) = @_; $file ||= $self->{file} || __FILE__; my %user_vars; my $user_vars = $self->has('set-vars') ? $self->get('set-vars') : undef; if ( $user_vars ) { foreach my $var_val ( @$user_vars ) { my ($var, $val) = $var_val =~ m/([^\s=]+)=(\S+)/; die "Invalid --set-vars value: $var_val\n" unless $var && defined $val; $user_vars{$var} = { val => $val, default => 0, }; } } my %default_vars; my $default_vars = $self->read_para_after($file, qr/MAGIC_set_vars/); if ( $default_vars ) { %default_vars = map { my $var_val = $_; my ($var, $val) = $var_val =~ m/([^\s=]+)=(\S+)/; die "Invalid --set-vars value: $var_val\n" unless $var && defined $val; $var => { val => $val, default => 1, }; } split("\n", $default_vars); } my %vars = ( %default_vars, # first the tool's defaults %user_vars, # then the user's which overwrite the defaults ); PTDEBUG && _d('--set-vars:', Dumper(\%vars)); return \%vars; } sub _d { my ($package, undef, $line) = caller 0; @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; } map { defined $_ ? $_ : 'undef' } @_; print STDERR "# $package:$line $PID ", join(' ', @_), "\n"; } if ( PTDEBUG ) { print STDERR '# ', $^X, ' ', $], "\n"; if ( my $uname = `uname -a` ) { $uname =~ s/\s+/ /g; print STDERR "# $uname\n"; } print STDERR '# Arguments: ', join(' ', map { my $a = "_[$_]_"; $a =~ s/\n/\n# /g; $a; } @ARGV), "\n"; } 1; } # ########################################################################### # End OptionParser package # ########################################################################### # ########################################################################### # Quoter package # This package is a copy without comments from the original. The original # with comments and its test file can be found in the Bazaar repository at, # lib/Quoter.pm # t/lib/Quoter.t # See https://launchpad.net/percona-toolkit for more information. # ########################################################################### { package Quoter; use strict; use warnings FATAL => 'all'; use English qw(-no_match_vars); use constant PTDEBUG => $ENV{PTDEBUG} || 0; use Data::Dumper; $Data::Dumper::Indent = 1; $Data::Dumper::Sortkeys = 1; $Data::Dumper::Quotekeys = 0; sub new { my ( $class, %args ) = @_; return bless {}, $class; } sub quote { my ( $self, @vals ) = @_; foreach my $val ( @vals ) { $val =~ s/`/``/g; } return join('.', map { '`' . $_ . '`' } @vals); } sub quote_val { my ( $self, $val, %args ) = @_; return 'NULL' unless defined $val; # undef = NULL return "''" if $val eq ''; # blank string = '' return $val if $val =~ m/^0x[0-9a-fA-F]+$/ # quote hex data && !$args{is_char}; # unless is_char is true $val =~ s/(['\\])/\\$1/g; return "'$val'"; } sub split_unquote { my ( $self, $db_tbl, $default_db ) = @_; my ( $db, $tbl ) = split(/[.]/, $db_tbl); if ( !$tbl ) { $tbl = $db; $db = $default_db; } for ($db, $tbl) { next unless $_; s/\A`//; s/`\z//; s/``/`/g; } return ($db, $tbl); } sub literal_like { my ( $self, $like ) = @_; return unless $like; $like =~ s/([%_])/\\$1/g; return "'$like'"; } sub join_quote { my ( $self, $default_db, $db_tbl ) = @_; return unless $db_tbl; my ($db, $tbl) = split(/[.]/, $db_tbl); if ( !$tbl ) { $tbl = $db; $db = $default_db; } $db = "`$db`" if $db && $db !~ m/^`/; $tbl = "`$tbl`" if $tbl && $tbl !~ m/^`/; return $db ? "$db.$tbl" : $tbl; } sub serialize_list { my ( $self, @args ) = @_; PTDEBUG && _d('Serializing', Dumper(\@args)); return unless @args; my @parts; foreach my $arg ( @args ) { if ( defined $arg ) { $arg =~ s/,/\\,/g; # escape commas $arg =~ s/\\N/\\\\N/g; # escape literal \N push @parts, $arg; } else { push @parts, '\N'; } } my $string = join(',', @parts); PTDEBUG && _d('Serialized: <', $string, '>'); return $string; } sub deserialize_list { my ( $self, $string ) = @_; PTDEBUG && _d('Deserializing <', $string, '>'); die "Cannot deserialize an undefined string" unless defined $string; my @parts; foreach my $arg ( split(/(?<!\\),/, $string) ) { if ( $arg eq '\N' ) { $arg = undef; } else { $arg =~ s/\\,/,/g; $arg =~ s/\\\\N/\\N/g; } push @parts, $arg; } if ( !@parts ) { my $n_empty_strings = $string =~ tr/,//; $n_empty_strings++; PTDEBUG && _d($n_empty_strings, 'empty strings'); map { push @parts, '' } 1..$n_empty_strings; } elsif ( $string =~ m/(?<!\\),$/ ) { PTDEBUG && _d('Last value is an empty string'); push @parts, ''; } PTDEBUG && _d('Deserialized', Dumper(\@parts)); return @parts; } sub _d { my ($package, undef, $line) = caller 0; @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; } map { defined $_ ? $_ : 'undef' } @_; print STDERR "# $package:$line $PID ", join(' ', @_), "\n"; } 1; } # ########################################################################### # End Quoter package # ########################################################################### # ########################################################################### # TableParser package # This package is a copy without comments from the original. The original # with comments and its test file can be found in the Bazaar repository at, # lib/TableParser.pm # t/lib/TableParser.t # See https://launchpad.net/percona-toolkit for more information. # ########################################################################### { package TableParser; use strict; use warnings FATAL => 'all'; use English qw(-no_match_vars); use constant PTDEBUG => $ENV{PTDEBUG} || 0; use Data::Dumper; $Data::Dumper::Indent = 1; $Data::Dumper::Sortkeys = 1; $Data::Dumper::Quotekeys = 0; local $EVAL_ERROR; eval { require Quoter; }; sub new { my ( $class, %args ) = @_; my $self = { %args }; $self->{Quoter} ||= Quoter->new(); return bless $self, $class; } sub Quoter { shift->{Quoter} } sub get_create_table { my ( $self, $dbh, $db, $tbl ) = @_; die "I need a dbh parameter" unless $dbh; die "I need a db parameter" unless $db; die "I need a tbl parameter" unless $tbl; my $q = $self->{Quoter}; my $new_sql_mode = q{/*!40101 SET @OLD_SQL_MODE := @@SQL_MODE, } . q{@@SQL_MODE := '', } . q{@OLD_QUOTE := @@SQL_QUOTE_SHOW_CREATE, } . q{@@SQL_QUOTE_SHOW_CREATE := 1 */}; my $old_sql_mode = q{/*!40101 SET @@SQL_MODE := @OLD_SQL_MODE, } . q{@@SQL_QUOTE_SHOW_CREATE := @OLD_QUOTE */}; PTDEBUG && _d($new_sql_mode); eval { $dbh->do($new_sql_mode); }; PTDEBUG && $EVAL_ERROR && _d($EVAL_ERROR); my $use_sql = 'USE ' . $q->quote($db); PTDEBUG && _d($dbh, $use_sql); $dbh->do($use_sql); my $show_sql = "SHOW CREATE TABLE " . $q->quote($db, $tbl); PTDEBUG && _d($show_sql); my $href; eval { $href = $dbh->selectrow_hashref($show_sql); }; if ( my $e = $EVAL_ERROR ) { PTDEBUG && _d($old_sql_mode); $dbh->do($old_sql_mode); die $e; } PTDEBUG && _d($old_sql_mode); $dbh->do($old_sql_mode); my ($key) = grep { m/create (?:table|view)/i } keys %$href; if ( !$key ) { die "Error: no 'Create Table' or 'Create View' in result set from " . "$show_sql: " . Dumper($href); } return $href->{$key}; } sub parse { my ( $self, $ddl, $opts ) = @_; return unless $ddl; if ( $ddl =~ m/CREATE (?:TEMPORARY )?TABLE "/ ) { $ddl = $self->ansi_to_legacy($ddl); } elsif ( $ddl !~ m/CREATE (?:TEMPORARY )?TABLE `/ ) { die "TableParser doesn't handle CREATE TABLE without quoting."; } my ($name) = $ddl =~ m/CREATE (?:TEMPORARY )?TABLE\s+(`.+?`)/; (undef, $name) = $self->{Quoter}->split_unquote($name) if $name; $ddl =~ s/(`[^`]+`)/\L$1/g; my $engine = $self->get_engine($ddl); my @defs = $ddl =~ m/^(\s+`.*?),?$/gm; my @cols = map { $_ =~ m/`([^`]+)`/ } @defs; PTDEBUG && _d('Table cols:', join(', ', map { "`$_`" } @cols)); my %def_for; @def_for{@cols} = @defs; my (@nums, @null); my (%type_for, %is_nullable, %is_numeric, %is_autoinc); foreach my $col ( @cols ) { my $def = $def_for{$col}; $def =~ s/``//g; my ( $type ) = $def =~ m/`[^`]+`\s([a-z]+)/; die "Can't determine column type for $def" unless $type; $type_for{$col} = $type; if ( $type =~ m/(?:(?:tiny|big|medium|small)?int|float|double|decimal|year)/ ) { push @nums, $col; $is_numeric{$col} = 1; } if ( $def !~ m/NOT NULL/ ) { push @null, $col; $is_nullable{$col} = 1; } $is_autoinc{$col} = $def =~ m/AUTO_INCREMENT/i ? 1 : 0; } my ($keys, $clustered_key) = $self->get_keys($ddl, $opts, \%is_nullable); my ($charset) = $ddl =~ m/DEFAULT CHARSET=(\w+)/; return { name => $name, cols => \@cols, col_posn => { map { $cols[$_] => $_ } 0..$#cols }, is_col => { map { $_ => 1 } @cols }, null_cols => \@null, is_nullable => \%is_nullable, is_autoinc => \%is_autoinc, clustered_key => $clustered_key, keys => $keys, defs => \%def_for, numeric_cols => \@nums, is_numeric => \%is_numeric, engine => $engine, type_for => \%type_for, charset => $charset, }; } sub sort_indexes { my ( $self, $tbl ) = @_; my @indexes = sort { (($a ne 'PRIMARY') <=> ($b ne 'PRIMARY')) || ( !$tbl->{keys}->{$a}->{is_unique} <=> !$tbl->{keys}->{$b}->{is_unique} ) || ( $tbl->{keys}->{$a}->{is_nullable} <=> $tbl->{keys}->{$b}->{is_nullable} ) || ( scalar(@{$tbl->{keys}->{$a}->{cols}}) <=> scalar(@{$tbl->{keys}->{$b}->{cols}}) ) } grep { $tbl->{keys}->{$_}->{type} eq 'BTREE' } sort keys %{$tbl->{keys}}; PTDEBUG && _d('Indexes sorted best-first:', join(', ', @indexes)); return @indexes; } sub find_best_index { my ( $self, $tbl, $index ) = @_; my $best; if ( $index ) { ($best) = grep { uc $_ eq uc $index } keys %{$tbl->{keys}}; } if ( !$best ) { if ( $index ) { die "Index '$index' does not exist in table"; } else { ($best) = $self->sort_indexes($tbl); } } PTDEBUG && _d('Best index found is', $best); return $best; } sub find_possible_keys { my ( $self, $dbh, $database, $table, $quoter, $where ) = @_; return () unless $where; my $sql = 'EXPLAIN SELECT * FROM ' . $quoter->quote($database, $table) . ' WHERE ' . $where; PTDEBUG && _d($sql); my $expl = $dbh->selectrow_hashref($sql); $expl = { map { lc($_) => $expl->{$_} } keys %$expl }; if ( $expl->{possible_keys} ) { PTDEBUG && _d('possible_keys =', $expl->{possible_keys}); my @candidates = split(',', $expl->{possible_keys}); my %possible = map { $_ => 1 } @candidates; if ( $expl->{key} ) { PTDEBUG && _d('MySQL chose', $expl->{key}); unshift @candidates, grep { $possible{$_} } split(',', $expl->{key}); PTDEBUG && _d('Before deduping:', join(', ', @candidates)); my %seen; @candidates = grep { !$seen{$_}++ } @candidates; } PTDEBUG && _d('Final list:', join(', ', @candidates)); return @candidates; } else { PTDEBUG && _d('No keys in possible_keys'); return (); } } sub check_table { my ( $self, %args ) = @_; my @required_args = qw(dbh db tbl); foreach my $arg ( @required_args ) { die "I need a $arg argument" unless $args{$arg}; } my ($dbh, $db, $tbl) = @args{@required_args}; my $q = $self->{Quoter} || 'Quoter'; my $db_tbl = $q->quote($db, $tbl); PTDEBUG && _d('Checking', $db_tbl); my $sql = "SHOW TABLES FROM " . $q->quote($db) . ' LIKE ' . $q->literal_like($tbl); PTDEBUG && _d($sql); my $row; eval { $row = $dbh->selectrow_arrayref($sql); }; if ( $EVAL_ERROR ) { PTDEBUG && _d($EVAL_ERROR); return 0; } if ( !$row->[0] || $row->[0] ne $tbl ) { PTDEBUG && _d('Table does not exist'); return 0; } PTDEBUG && _d('Table', $db, $tbl, 'exists'); return 1; } sub get_engine { my ( $self, $ddl, $opts ) = @_; my ( $engine ) = $ddl =~ m/\).*?(?:ENGINE|TYPE)=(\w+)/; PTDEBUG && _d('Storage engine:', $engine); return $engine || undef; } sub get_keys { my ( $self, $ddl, $opts, $is_nullable ) = @_; my $engine = $self->get_engine($ddl); my $keys = {}; my $clustered_key = undef; KEY: foreach my $key ( $ddl =~ m/^ ((?:[A-Z]+ )?KEY .*)$/gm ) { next KEY if $key =~ m/FOREIGN/; my $key_ddl = $key; PTDEBUG && _d('Parsed key:', $key_ddl); if ( !$engine || $engine !~ m/MEMORY|HEAP/ ) { $key =~ s/USING HASH/USING BTREE/; } my ( $type, $cols ) = $key =~ m/(?:USING (\w+))? \((.+)\)/; my ( $special ) = $key =~ m/(FULLTEXT|SPATIAL)/; $type = $type || $special || 'BTREE'; my ($name) = $key =~ m/(PRIMARY|`[^`]*`)/; my $unique = $key =~ m/PRIMARY|UNIQUE/ ? 1 : 0; my @cols; my @col_prefixes; foreach my $col_def ( $cols =~ m/`[^`]+`(?:\(\d+\))?/g ) { my ($name, $prefix) = $col_def =~ m/`([^`]+)`(?:\((\d+)\))?/; push @cols, $name; push @col_prefixes, $prefix; } $name =~ s/`//g; PTDEBUG && _d( $name, 'key cols:', join(', ', map { "`$_`" } @cols)); $keys->{$name} = { name => $name, type => $type, colnames => $cols, cols => \@cols, col_prefixes => \@col_prefixes, is_unique => $unique, is_nullable => scalar(grep { $is_nullable->{$_} } @cols), is_col => { map { $_ => 1 } @cols }, ddl => $key_ddl, }; if ( ($engine || '') =~ m/InnoDB/i && !$clustered_key ) { my $this_key = $keys->{$name}; if ( $this_key->{name} eq 'PRIMARY' ) { $clustered_key = 'PRIMARY'; } elsif ( $this_key->{is_unique} && !$this_key->{is_nullable} ) { $clustered_key = $this_key->{name}; } PTDEBUG && $clustered_key && _d('This key is the clustered key'); } } return $keys, $clustered_key; } sub get_fks { my ( $self, $ddl, $opts ) = @_; my $q = $self->{Quoter}; my $fks = {}; foreach my $fk ( $ddl =~ m/CONSTRAINT .* FOREIGN KEY .* REFERENCES [^\)]*\)/mg ) { my ( $name ) = $fk =~ m/CONSTRAINT `(.*?)`/; my ( $cols ) = $fk =~ m/FOREIGN KEY \(([^\)]+)\)/; my ( $parent, $parent_cols ) = $fk =~ m/REFERENCES (\S+) \(([^\)]+)\)/; my ($db, $tbl) = $q->split_unquote($parent, $opts->{database}); my %parent_tbl = (tbl => $tbl); $parent_tbl{db} = $db if $db; if ( $parent !~ m/\./ && $opts->{database} ) { $parent = $q->quote($opts->{database}) . ".$parent"; } $fks->{$name} = { name => $name, colnames => $cols, cols => [ map { s/[ `]+//g; $_; } split(',', $cols) ], parent_tbl => \%parent_tbl, parent_tblname => $parent, parent_cols => [ map { s/[ `]+//g; $_; } split(',', $parent_cols) ], parent_colnames=> $parent_cols, ddl => $fk, }; } return $fks; } sub remove_auto_increment { my ( $self, $ddl ) = @_; $ddl =~ s/(^\).*?) AUTO_INCREMENT=\d+\b/$1/m; return $ddl; } sub get_table_status { my ( $self, $dbh, $db, $like ) = @_; my $q = $self->{Quoter}; my $sql = "SHOW TABLE STATUS FROM " . $q->quote($db); my @params; if ( $like ) { $sql .= ' LIKE ?'; push @params, $like; } PTDEBUG && _d($sql, @params); my $sth = $dbh->prepare($sql); eval { $sth->execute(@params); }; if ($EVAL_ERROR) { PTDEBUG && _d($EVAL_ERROR); return; } my @tables = @{$sth->fetchall_arrayref({})}; @tables = map { my %tbl; # Make a copy with lowercased keys @tbl{ map { lc $_ } keys %$_ } = values %$_; $tbl{engine} ||= $tbl{type} || $tbl{comment}; delete $tbl{type}; \%tbl; } @tables; return @tables; } my $ansi_quote_re = qr/" [^"]* (?: "" [^"]* )* (?<=.) "/ismx; sub ansi_to_legacy { my ($self, $ddl) = @_; $ddl =~ s/($ansi_quote_re)/ansi_quote_replace($1)/ge; return $ddl; } sub ansi_quote_replace { my ($val) = @_; $val =~ s/^"|"$//g; $val =~ s/`/``/g; $val =~ s/""/"/g; return "`$val`"; } sub _d { my ($package, undef, $line) = caller 0; @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; } map { defined $_ ? $_ : 'undef' } @_; print STDERR "# $package:$line $PID ", join(' ', @_), "\n"; } 1; } # ########################################################################### # End TableParser package # ########################################################################### # ########################################################################### # Daemon package # This package is a copy without comments from the original. The original # with comments and its test file can be found in the Bazaar repository at, # lib/Daemon.pm # t/lib/Daemon.t # See https://launchpad.net/percona-toolkit for more information. # ########################################################################### { package Daemon; use strict; use warnings FATAL => 'all'; use English qw(-no_match_vars); use constant PTDEBUG => $ENV{PTDEBUG} || 0; use POSIX qw(setsid); sub new { my ( $class, %args ) = @_; foreach my $arg ( qw(o) ) { die "I need a $arg argument" unless $args{$arg}; } my $o = $args{o}; my $self = { o => $o, log_file => $o->has('log') ? $o->get('log') : undef, PID_file => $o->has('pid') ? $o->get('pid') : undef, }; check_PID_file(undef, $self->{PID_file}); PTDEBUG && _d('Daemonized child will log to', $self->{log_file}); return bless $self, $class; } sub daemonize { my ( $self ) = @_; PTDEBUG && _d('About to fork and daemonize'); defined (my $pid = fork()) or die "Cannot fork: $OS_ERROR"; if ( $pid ) { PTDEBUG && _d('Parent PID', $PID, 'exiting after forking child PID',$pid); exit; } PTDEBUG && _d('Daemonizing child PID', $PID); $self->{PID_owner} = $PID; $self->{child} = 1; POSIX::setsid() or die "Cannot start a new session: $OS_ERROR"; chdir '/' or die "Cannot chdir to /: $OS_ERROR"; $self->_make_PID_file(); $OUTPUT_AUTOFLUSH = 1; PTDEBUG && _d('Redirecting STDIN to /dev/null'); close STDIN; open STDIN, '/dev/null' or die "Cannot reopen STDIN to /dev/null: $OS_ERROR"; if ( $self->{log_file} ) { PTDEBUG && _d('Redirecting STDOUT and STDERR to', $self->{log_file}); close STDOUT; open STDOUT, '>>', $self->{log_file} or die "Cannot open log file $self->{log_file}: $OS_ERROR"; close STDERR; open STDERR, ">&STDOUT" or die "Cannot dupe STDERR to STDOUT: $OS_ERROR"; } else { if ( -t STDOUT ) { PTDEBUG && _d('No log file and STDOUT is a terminal;', 'redirecting to /dev/null'); close STDOUT; open STDOUT, '>', '/dev/null' or die "Cannot reopen STDOUT to /dev/null: $OS_ERROR"; } if ( -t STDERR ) { PTDEBUG && _d('No log file and STDERR is a terminal;', 'redirecting to /dev/null'); close STDERR; open STDERR, '>', '/dev/null' or die "Cannot reopen STDERR to /dev/null: $OS_ERROR"; } } return; } sub check_PID_file { my ( $self, $file ) = @_; my $PID_file = $self ? $self->{PID_file} : $file; PTDEBUG && _d('Checking PID file', $PID_file); if ( $PID_file && -f $PID_file ) { my $pid; eval { chomp($pid = (slurp_file($PID_file) || '')); }; if ( $EVAL_ERROR ) { die "The PID file $PID_file already exists but it cannot be read: " . $EVAL_ERROR; } PTDEBUG && _d('PID file exists; it contains PID', $pid); if ( $pid ) { my $pid_is_alive = kill 0, $pid; if ( $pid_is_alive ) { die "The PID file $PID_file already exists " . " and the PID that it contains, $pid, is running"; } else { warn "Overwriting PID file $PID_file because the PID that it " . "contains, $pid, is not running"; } } else { die "The PID file $PID_file already exists but it does not " . "contain a PID"; } } else { PTDEBUG && _d('No PID file'); } return; } sub make_PID_file { my ( $self ) = @_; if ( exists $self->{child} ) { die "Do not call Daemon::make_PID_file() for daemonized scripts"; } $self->_make_PID_file(); $self->{PID_owner} = $PID; return; } sub _make_PID_file { my ( $self ) = @_; my $PID_file = $self->{PID_file}; if ( !$PID_file ) { PTDEBUG && _d('No PID file to create'); return; } $self->check_PID_file(); open my $PID_FH, '>', $PID_file or die "Cannot open PID file $PID_file: $OS_ERROR"; print $PID_FH $PID or die "Cannot print to PID file $PID_file: $OS_ERROR"; close $PID_FH or die "Cannot close PID file $PID_file: $OS_ERROR"; PTDEBUG && _d('Created PID file:', $self->{PID_file}); return; } sub _remove_PID_file { my ( $self ) = @_; if ( $self->{PID_file} && -f $self->{PID_file} ) { unlink $self->{PID_file} or warn "Cannot remove PID file $self->{PID_file}: $OS_ERROR"; PTDEBUG && _d('Removed PID file'); } else { PTDEBUG && _d('No PID to remove'); } return; } sub DESTROY { my ( $self ) = @_; $self->_remove_PID_file() if ($self->{PID_owner} || 0) == $PID; return; } sub slurp_file { my ($file) = @_; return unless $file; open my $fh, "<", $file or die "Cannot open $file: $OS_ERROR"; return do { local $/; <$fh> }; } sub _d { my ($package, undef, $line) = caller 0; @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; } map { defined $_ ? $_ : 'undef' } @_; print STDERR "# $package:$line $PID ", join(' ', @_), "\n"; } 1; } # ########################################################################### # End Daemon package # ########################################################################### # ########################################################################### # HTTP::Micro package # This package is a copy without comments from the original. The original # with comments and its test file can be found in the Bazaar repository at, # lib/HTTP/Micro.pm # t/lib/HTTP/Micro.t # See https://launchpad.net/percona-toolkit for more information. # ########################################################################### { package HTTP::Micro; our $VERSION = '0.01'; use strict; use warnings FATAL => 'all'; use English qw(-no_match_vars); use Carp (); my @attributes; BEGIN { @attributes = qw(agent timeout); no strict 'refs'; for my $accessor ( @attributes ) { *{$accessor} = sub { @_ > 1 ? $_[0]->{$accessor} = $_[1] : $_[0]->{$accessor}; }; } } sub new { my($class, %args) = @_; (my $agent = $class) =~ s{::}{-}g; my $self = { agent => $agent . "/" . ($class->VERSION || 0), timeout => 60, }; for my $key ( @attributes ) { $self->{$key} = $args{$key} if exists $args{$key} } return bless $self, $class; } my %DefaultPort = ( http => 80, https => 443, ); sub request { my ($self, $method, $url, $args) = @_; @_ == 3 || (@_ == 4 && ref $args eq 'HASH') or Carp::croak(q/Usage: $http->request(METHOD, URL, [HASHREF])/); $args ||= {}; # we keep some state in this during _request my $response; for ( 0 .. 1 ) { $response = eval { $self->_request($method, $url, $args) }; last unless $@ && $method eq 'GET' && $@ =~ m{^(?:Socket closed|Unexpected end)}; } if (my $e = "$@") { $response = { success => q{}, status => 599, reason => 'Internal Exception', content => $e, headers => { 'content-type' => 'text/plain', 'content-length' => length $e, } }; } return $response; } sub _request { my ($self, $method, $url, $args) = @_; my ($scheme, $host, $port, $path_query) = $self->_split_url($url); my $request = { method => $method, scheme => $scheme, host_port => ($port == $DefaultPort{$scheme} ? $host : "$host:$port"), uri => $path_query, headers => {}, }; my $handle = HTTP::Micro::Handle->new(timeout => $self->{timeout}); $handle->connect($scheme, $host, $port); $self->_prepare_headers_and_cb($request, $args); $handle->write_request_header(@{$request}{qw/method uri headers/}); $handle->write_content_body($request) if $request->{content}; my $response; do { $response = $handle->read_response_header } until (substr($response->{status},0,1) ne '1'); if (!($method eq 'HEAD' || $response->{status} =~ /^[23]04/)) { $response->{content} = ''; $handle->read_content_body(sub { $_[1]->{content} .= $_[0] }, $response); } $handle->close; $response->{success} = substr($response->{status},0,1) eq '2'; return $response; } sub _prepare_headers_and_cb { my ($self, $request, $args) = @_; for ($args->{headers}) { next unless defined; while (my ($k, $v) = each %$_) { $request->{headers}{lc $k} = $v; } } $request->{headers}{'host'} = $request->{host_port}; $request->{headers}{'connection'} = "close"; $request->{headers}{'user-agent'} ||= $self->{agent}; if (defined $args->{content}) { $request->{headers}{'content-type'} ||= "application/octet-stream"; utf8::downgrade($args->{content}, 1) or Carp::croak(q/Wide character in request message body/); $request->{headers}{'content-length'} = length $args->{content}; $request->{content} = $args->{content}; } return; } sub _split_url { my $url = pop; my ($scheme, $authority, $path_query) = $url =~ m<\A([^:/?#]+)://([^/?#]*)([^#]*)> or Carp::croak(qq/Cannot parse URL: '$url'/); $scheme = lc $scheme; $path_query = "/$path_query" unless $path_query =~ m<\A/>; my $host = (length($authority)) ? lc $authority : 'localhost'; $host =~ s/\A[^@]*@//; # userinfo my $port = do { $host =~ s/:([0-9]*)\z// && length $1 ? $1 : $DefaultPort{$scheme} }; return ($scheme, $host, $port, $path_query); } } # HTTP::Micro { package HTTP::Micro::Handle; use strict; use warnings FATAL => 'all'; use English qw(-no_match_vars); use Carp qw(croak); use Errno qw(EINTR EPIPE); use IO::Socket qw(SOCK_STREAM); sub BUFSIZE () { 32768 } my $Printable = sub { local $_ = shift; s/\r/\\r/g; s/\n/\\n/g; s/\t/\\t/g; s/([^\x20-\x7E])/sprintf('\\x%.2X', ord($1))/ge; $_; }; sub new { my ($class, %args) = @_; return bless { rbuf => '', timeout => 60, max_line_size => 16384, %args }, $class; } my $ssl_verify_args = { check_cn => "when_only", wildcards_in_alt => "anywhere", wildcards_in_cn => "anywhere" }; sub connect { @_ == 4 || croak(q/Usage: $handle->connect(scheme, host, port)/); my ($self, $scheme, $host, $port) = @_; if ( $scheme eq 'https' ) { eval "require IO::Socket::SSL" unless exists $INC{'IO/Socket/SSL.pm'}; croak(qq/IO::Socket::SSL must be installed for https support\n/) unless $INC{'IO/Socket/SSL.pm'}; } elsif ( $scheme ne 'http' ) { croak(qq/Unsupported URL scheme '$scheme'\n/); } $self->{fh} = IO::Socket::INET->new( PeerHost => $host, PeerPort => $port, Proto => 'tcp', Type => SOCK_STREAM, Timeout => $self->{timeout} ) or croak(qq/Could not connect to '$host:$port': $@/); binmode($self->{fh}) or croak(qq/Could not binmode() socket: '$!'/); if ( $scheme eq 'https') { IO::Socket::SSL->start_SSL($self->{fh}); ref($self->{fh}) eq 'IO::Socket::SSL' or die(qq/SSL connection failed for $host\n/); if ( $self->{fh}->can("verify_hostname") ) { $self->{fh}->verify_hostname( $host, $ssl_verify_args ) or die(qq/SSL certificate not valid for $host\n/); } else { my $fh = $self->{fh}; _verify_hostname_of_cert($host, _peer_certificate($fh), $ssl_verify_args) or die(qq/SSL certificate not valid for $host\n/); } } $self->{host} = $host; $self->{port} = $port; return $self; } sub close { @_ == 1 || croak(q/Usage: $handle->close()/); my ($self) = @_; CORE::close($self->{fh}) or croak(qq/Could not close socket: '$!'/); } sub write { @_ == 2 || croak(q/Usage: $handle->write(buf)/); my ($self, $buf) = @_; my $len = length $buf; my $off = 0; local $SIG{PIPE} = 'IGNORE'; while () { $self->can_write or croak(q/Timed out while waiting for socket to become ready for writing/); my $r = syswrite($self->{fh}, $buf, $len, $off); if (defined $r) { $len -= $r; $off += $r; last unless $len > 0; } elsif ($! == EPIPE) { croak(qq/Socket closed by remote server: $!/); } elsif ($! != EINTR) { croak(qq/Could not write to socket: '$!'/); } } return $off; } sub read { @_ == 2 || @_ == 3 || croak(q/Usage: $handle->read(len)/); my ($self, $len) = @_; my $buf = ''; my $got = length $self->{rbuf}; if ($got) { my $take = ($got < $len) ? $got : $len; $buf = substr($self->{rbuf}, 0, $take, ''); $len -= $take; } while ($len > 0) { $self->can_read or croak(q/Timed out while waiting for socket to become ready for reading/); my $r = sysread($self->{fh}, $buf, $len, length $buf); if (defined $r) { last unless $r; $len -= $r; } elsif ($! != EINTR) { croak(qq/Could not read from socket: '$!'/); } } if ($len) { croak(q/Unexpected end of stream/); } return $buf; } sub readline { @_ == 1 || croak(q/Usage: $handle->readline()/); my ($self) = @_; while () { if ($self->{rbuf} =~ s/\A ([^\x0D\x0A]* \x0D?\x0A)//x) { return $1; } $self->can_read or croak(q/Timed out while waiting for socket to become ready for reading/); my $r = sysread($self->{fh}, $self->{rbuf}, BUFSIZE, length $self->{rbuf}); if (defined $r) { last unless $r; } elsif ($! != EINTR) { croak(qq/Could not read from socket: '$!'/); } } croak(q/Unexpected end of stream while looking for line/); } sub read_header_lines { @_ == 1 || @_ == 2 || croak(q/Usage: $handle->read_header_lines([headers])/); my ($self, $headers) = @_; $headers ||= {}; my $lines = 0; my $val; while () { my $line = $self->readline; if ($line =~ /\A ([^\x00-\x1F\x7F:]+) : [\x09\x20]* ([^\x0D\x0A]*)/x) { my ($field_name) = lc $1; $val = \($headers->{$field_name} = $2); } elsif ($line =~ /\A [\x09\x20]+ ([^\x0D\x0A]*)/x) { $val or croak(q/Unexpected header continuation line/); next unless length $1; $$val .= ' ' if length $$val; $$val .= $1; } elsif ($line =~ /\A \x0D?\x0A \z/x) { last; } else { croak(q/Malformed header line: / . $Printable->($line)); } } return $headers; } sub write_header_lines { (@_ == 2 && ref $_[1] eq 'HASH') || croak(q/Usage: $handle->write_header_lines(headers)/); my($self, $headers) = @_; my $buf = ''; while (my ($k, $v) = each %$headers) { my $field_name = lc $k; $field_name =~ /\A [\x21\x23-\x27\x2A\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7A\x7C\x7E]+ \z/x or croak(q/Invalid HTTP header field name: / . $Printable->($field_name)); $field_name =~ s/\b(\w)/\u$1/g; $buf .= "$field_name: $v\x0D\x0A"; } $buf .= "\x0D\x0A"; return $self->write($buf); } sub read_content_body { @_ == 3 || @_ == 4 || croak(q/Usage: $handle->read_content_body(callback, response, [read_length])/); my ($self, $cb, $response, $len) = @_; $len ||= $response->{headers}{'content-length'}; croak("No content-length in the returned response, and this " . "UA doesn't implement chunking") unless defined $len; while ($len > 0) { my $read = ($len > BUFSIZE) ? BUFSIZE : $len; $cb->($self->read($read), $response); $len -= $read; } return; } sub write_content_body { @_ == 2 || croak(q/Usage: $handle->write_content_body(request)/); my ($self, $request) = @_; my ($len, $content_length) = (0, $request->{headers}{'content-length'}); $len += $self->write($request->{content}); $len == $content_length or croak(qq/Content-Length missmatch (got: $len expected: $content_length)/); return $len; } sub read_response_header { @_ == 1 || croak(q/Usage: $handle->read_response_header()/); my ($self) = @_; my $line = $self->readline; $line =~ /\A (HTTP\/(0*\d+\.0*\d+)) [\x09\x20]+ ([0-9]{3}) [\x09\x20]+ ([^\x0D\x0A]*) \x0D?\x0A/x or croak(q/Malformed Status-Line: / . $Printable->($line)); my ($protocol, $version, $status, $reason) = ($1, $2, $3, $4); return { status => $status, reason => $reason, headers => $self->read_header_lines, protocol => $protocol, }; } sub write_request_header { @_ == 4 || croak(q/Usage: $handle->write_request_header(method, request_uri, headers)/); my ($self, $method, $request_uri, $headers) = @_; return $self->write("$method $request_uri HTTP/1.1\x0D\x0A") + $self->write_header_lines($headers); } sub _do_timeout { my ($self, $type, $timeout) = @_; $timeout = $self->{timeout} unless defined $timeout && $timeout >= 0; my $fd = fileno $self->{fh}; defined $fd && $fd >= 0 or croak(q/select(2): 'Bad file descriptor'/); my $initial = time; my $pending = $timeout; my $nfound; vec(my $fdset = '', $fd, 1) = 1; while () { $nfound = ($type eq 'read') ? select($fdset, undef, undef, $pending) : select(undef, $fdset, undef, $pending) ; if ($nfound == -1) { $! == EINTR or croak(qq/select(2): '$!'/); redo if !$timeout || ($pending = $timeout - (time - $initial)) > 0; $nfound = 0; } last; } $! = 0; return $nfound; } sub can_read { @_ == 1 || @_ == 2 || croak(q/Usage: $handle->can_read([timeout])/); my $self = shift; return $self->_do_timeout('read', @_) } sub can_write { @_ == 1 || @_ == 2 || croak(q/Usage: $handle->can_write([timeout])/); my $self = shift; return $self->_do_timeout('write', @_) } } # HTTP::Micro::Handle my $prog = <<'EOP'; BEGIN { if ( defined &IO::Socket::SSL::CAN_IPV6 ) { *CAN_IPV6 = \*IO::Socket::SSL::CAN_IPV6; } else { constant->import( CAN_IPV6 => '' ); } my %const = ( NID_CommonName => 13, GEN_DNS => 2, GEN_IPADD => 7, ); while ( my ($name,$value) = each %const ) { no strict 'refs'; *{$name} = UNIVERSAL::can( 'Net::SSLeay', $name ) || sub { $value }; } } { use Carp qw(croak); my %dispatcher = ( issuer => sub { Net::SSLeay::X509_NAME_oneline( Net::SSLeay::X509_get_issuer_name( shift )) }, subject => sub { Net::SSLeay::X509_NAME_oneline( Net::SSLeay::X509_get_subject_name( shift )) }, ); if ( $Net::SSLeay::VERSION >= 1.30 ) { $dispatcher{commonName} = sub { my $cn = Net::SSLeay::X509_NAME_get_text_by_NID( Net::SSLeay::X509_get_subject_name( shift ), NID_CommonName); $cn =~s{\0$}{}; # work around Bug in Net::SSLeay <1.33 $cn; } } else { $dispatcher{commonName} = sub { croak "you need at least Net::SSLeay version 1.30 for getting commonName" } } if ( $Net::SSLeay::VERSION >= 1.33 ) { $dispatcher{subjectAltNames} = sub { Net::SSLeay::X509_get_subjectAltNames( shift ) }; } else { $dispatcher{subjectAltNames} = sub { return; }; } $dispatcher{authority} = $dispatcher{issuer}; $dispatcher{owner} = $dispatcher{subject}; $dispatcher{cn} = $dispatcher{commonName}; sub _peer_certificate { my ($self, $field) = @_; my $ssl = $self->_get_ssl_object or return; my $cert = ${*$self}{_SSL_certificate} ||= Net::SSLeay::get_peer_certificate($ssl) or return $self->error("Could not retrieve peer certificate"); if ($field) { my $sub = $dispatcher{$field} or croak "invalid argument for peer_certificate, valid are: ".join( " ",keys %dispatcher ). "\nMaybe you need to upgrade your Net::SSLeay"; return $sub->($cert); } else { return $cert } } my %scheme = ( ldap => { wildcards_in_cn => 0, wildcards_in_alt => 'leftmost', check_cn => 'always', }, http => { wildcards_in_cn => 'anywhere', wildcards_in_alt => 'anywhere', check_cn => 'when_only', }, smtp => { wildcards_in_cn => 0, wildcards_in_alt => 0, check_cn => 'always' }, none => {}, # do not check ); $scheme{www} = $scheme{http}; # alias $scheme{xmpp} = $scheme{http}; # rfc 3920 $scheme{pop3} = $scheme{ldap}; # rfc 2595 $scheme{imap} = $scheme{ldap}; # rfc 2595 $scheme{acap} = $scheme{ldap}; # rfc 2595 $scheme{nntp} = $scheme{ldap}; # rfc 4642 $scheme{ftp} = $scheme{http}; # rfc 4217 sub _verify_hostname_of_cert { my $identity = shift; my $cert = shift; my $scheme = shift || 'none'; if ( ! ref($scheme) ) { $scheme = $scheme{$scheme} or croak "scheme $scheme not defined"; } return 1 if ! %$scheme; # 'none' my $commonName = $dispatcher{cn}->($cert); my @altNames = $dispatcher{subjectAltNames}->($cert); if ( my $sub = $scheme->{callback} ) { return $sub->($identity,$commonName,@altNames); } my $ipn; if ( CAN_IPV6 and $identity =~m{:} ) { $ipn = IO::Socket::SSL::inet_pton(IO::Socket::SSL::AF_INET6,$identity) or croak "'$identity' is not IPv6, but neither IPv4 nor hostname"; } elsif ( $identity =~m{^\d+\.\d+\.\d+\.\d+$} ) { $ipn = IO::Socket::SSL::inet_aton( $identity ) or croak "'$identity' is not IPv4, but neither IPv6 nor hostname"; } else { if ( $identity =~m{[^a-zA-Z0-9_.\-]} ) { $identity =~m{\0} and croak("name '$identity' has \\0 byte"); $identity = IO::Socket::SSL::idn_to_ascii($identity) or croak "Warning: Given name '$identity' could not be converted to IDNA!"; } } my $check_name = sub { my ($name,$identity,$wtyp) = @_; $wtyp ||= ''; my $pattern; if ( $wtyp eq 'anywhere' and $name =~m{^([a-zA-Z0-9_\-]*)\*(.+)} ) { $pattern = qr{^\Q$1\E[a-zA-Z0-9_\-]*\Q$2\E$}i; } elsif ( $wtyp eq 'leftmost' and $name =~m{^\*(\..+)$} ) { $pattern = qr{^[a-zA-Z0-9_\-]*\Q$1\E$}i; } else { $pattern = qr{^\Q$name\E$}i; } return $identity =~ $pattern; }; my $alt_dnsNames = 0; while (@altNames) { my ($type, $name) = splice (@altNames, 0, 2); if ( $ipn and $type == GEN_IPADD ) { return 1 if $ipn eq $name; } elsif ( ! $ipn and $type == GEN_DNS ) { $name =~s/\s+$//; $name =~s/^\s+//; $alt_dnsNames++; $check_name->($name,$identity,$scheme->{wildcards_in_alt}) and return 1; } } if ( ! $ipn and ( $scheme->{check_cn} eq 'always' or $scheme->{check_cn} eq 'when_only' and !$alt_dnsNames)) { $check_name->($commonName,$identity,$scheme->{wildcards_in_cn}) and return 1; } return 0; # no match } } EOP eval { require IO::Socket::SSL }; if ( $INC{"IO/Socket/SSL.pm"} ) { eval $prog; die $@ if $@; } 1; # ########################################################################### # End HTTP::Micro package # ########################################################################### # ########################################################################### # VersionCheck package # This package is a copy without comments from the original. The original # with comments and its test file can be found in the Bazaar repository at, # lib/VersionCheck.pm # t/lib/VersionCheck.t # See https://launchpad.net/percona-toolkit for more information. # ########################################################################### { package VersionCheck; use strict; use warnings FATAL => 'all'; use English qw(-no_match_vars); use constant PTDEBUG => $ENV{PTDEBUG} || 0; use Data::Dumper; local $Data::Dumper::Indent = 1; local $Data::Dumper::Sortkeys = 1; local $Data::Dumper::Quotekeys = 0; use Digest::MD5 qw(md5_hex); use Sys::Hostname qw(hostname); use File::Basename qw(); use File::Spec; use FindBin qw(); eval { require Percona::Toolkit; require HTTP::Micro; }; { my $file = 'percona-version-check'; my $home = $ENV{HOME} || $ENV{HOMEPATH} || $ENV{USERPROFILE} || '.'; my @vc_dirs = ( '/etc/percona', '/etc/percona-toolkit', '/tmp', "$home", ); sub version_check_file { foreach my $dir ( @vc_dirs ) { if ( -d $dir && -w $dir ) { PTDEBUG && _d('Version check file', $file, 'in', $dir); return $dir . '/' . $file; } } PTDEBUG && _d('Version check file', $file, 'in', $ENV{PWD}); return $file; # in the CWD } } sub version_check_time_limit { return 60 * 60 * 24; # one day } sub version_check { my (%args) = @_; my $instances = $args{instances} || []; my $instances_to_check; PTDEBUG && _d('FindBin::Bin:', $FindBin::Bin); if ( !$args{force} ) { if ( $FindBin::Bin && (-d "$FindBin::Bin/../.bzr" || -d "$FindBin::Bin/../../.bzr" || -d "$FindBin::Bin/../.git" || -d "$FindBin::Bin/../../.git" ) ) { PTDEBUG && _d("$FindBin::Bin/../.bzr disables --version-check"); return; } } eval { foreach my $instance ( @$instances ) { my ($name, $id) = get_instance_id($instance); $instance->{name} = $name; $instance->{id} = $id; } push @$instances, { name => 'system', id => 0 }; $instances_to_check = get_instances_to_check( instances => $instances, vc_file => $args{vc_file}, # testing now => $args{now}, # testing ); PTDEBUG && _d(scalar @$instances_to_check, 'instances to check'); return unless @$instances_to_check; my $protocol = 'https'; eval { require IO::Socket::SSL; }; if ( $EVAL_ERROR ) { PTDEBUG && _d($EVAL_ERROR); PTDEBUG && _d("SSL not available, won't run version_check"); return; } PTDEBUG && _d('Using', $protocol); my $advice = pingback( instances => $instances_to_check, protocol => $protocol, url => $args{url} # testing || $ENV{PERCONA_VERSION_CHECK_URL} # testing || "$protocol://v.percona.com", ); if ( $advice ) { PTDEBUG && _d('Advice:', Dumper($advice)); if ( scalar @$advice > 1) { print "\n# " . scalar @$advice . " software updates are " . "available:\n"; } else { print "\n# A software update is available:\n"; } print join("\n", map { "# * $_" } @$advice), "\n\n"; } }; if ( $EVAL_ERROR ) { PTDEBUG && _d('Version check failed:', $EVAL_ERROR); } if ( @$instances_to_check ) { eval { update_check_times( instances => $instances_to_check, vc_file => $args{vc_file}, # testing now => $args{now}, # testing ); }; if ( $EVAL_ERROR ) { PTDEBUG && _d('Error updating version check file:', $EVAL_ERROR); } } if ( $ENV{PTDEBUG_VERSION_CHECK} ) { warn "Exiting because the PTDEBUG_VERSION_CHECK " . "environment variable is defined.\n"; exit 255; } return; } sub get_instances_to_check { my (%args) = @_; my $instances = $args{instances}; my $now = $args{now} || int(time); my $vc_file = $args{vc_file} || version_check_file(); if ( !-f $vc_file ) { PTDEBUG && _d('Version check file', $vc_file, 'does not exist;', 'version checking all instances'); return $instances; } open my $fh, '<', $vc_file or die "Cannot open $vc_file: $OS_ERROR"; chomp(my $file_contents = do { local $/ = undef; <$fh> }); PTDEBUG && _d('Version check file', $vc_file, 'contents:', $file_contents); close $fh; my %last_check_time_for = $file_contents =~ /^([^,]+),(.+)$/mg; my $check_time_limit = version_check_time_limit(); my @instances_to_check; foreach my $instance ( @$instances ) { my $last_check_time = $last_check_time_for{ $instance->{id} }; PTDEBUG && _d('Intsance', $instance->{id}, 'last checked', $last_check_time, 'now', $now, 'diff', $now - ($last_check_time || 0), 'hours until next check', sprintf '%.2f', ($check_time_limit - ($now - ($last_check_time || 0))) / 3600); if ( !defined $last_check_time || ($now - $last_check_time) >= $check_time_limit ) { PTDEBUG && _d('Time to check', Dumper($instance)); push @instances_to_check, $instance; } } return \@instances_to_check; } sub update_check_times { my (%args) = @_; my $instances = $args{instances}; my $now = $args{now} || int(time); my $vc_file = $args{vc_file} || version_check_file(); PTDEBUG && _d('Updating last check time:', $now); my %all_instances = map { $_->{id} => { name => $_->{name}, ts => $now } } @$instances; if ( -f $vc_file ) { open my $fh, '<', $vc_file or die "Cannot read $vc_file: $OS_ERROR"; my $contents = do { local $/ = undef; <$fh> }; close $fh; foreach my $line ( split("\n", ($contents || '')) ) { my ($id, $ts) = split(',', $line); if ( !exists $all_instances{$id} ) { $all_instances{$id} = { ts => $ts }; # original ts, not updated } } } open my $fh, '>', $vc_file or die "Cannot write to $vc_file: $OS_ERROR"; foreach my $id ( sort keys %all_instances ) { PTDEBUG && _d('Updated:', $id, Dumper($all_instances{$id})); print { $fh } $id . ',' . $all_instances{$id}->{ts} . "\n"; } close $fh; return; } sub get_instance_id { my ($instance) = @_; my $dbh = $instance->{dbh}; my $dsn = $instance->{dsn}; my $sql = q{SELECT CONCAT(@@hostname, @@port)}; PTDEBUG && _d($sql); my ($name) = eval { $dbh->selectrow_array($sql) }; if ( $EVAL_ERROR ) { PTDEBUG && _d($EVAL_ERROR); $sql = q{SELECT @@hostname}; PTDEBUG && _d($sql); ($name) = eval { $dbh->selectrow_array($sql) }; if ( $EVAL_ERROR ) { PTDEBUG && _d($EVAL_ERROR); $name = ($dsn->{h} || 'localhost') . ($dsn->{P} || 3306); } else { $sql = q{SHOW VARIABLES LIKE 'port'}; PTDEBUG && _d($sql); my (undef, $port) = eval { $dbh->selectrow_array($sql) }; PTDEBUG && _d('port:', $port); $name .= $port || ''; } } my $id = md5_hex($name); PTDEBUG && _d('MySQL instance:', $id, $name, Dumper($dsn)); return $name, $id; } sub pingback { my (%args) = @_; my @required_args = qw(url instances); foreach my $arg ( @required_args ) { die "I need a $arg arugment" unless $args{$arg}; } my $url = $args{url}; my $instances = $args{instances}; my $ua = $args{ua} || HTTP::Micro->new( timeout => 3 ); my $response = $ua->request('GET', $url); PTDEBUG && _d('Server response:', Dumper($response)); die "No response from GET $url" if !$response; die("GET on $url returned HTTP status $response->{status}; expected 200\n", ($response->{content} || '')) if $response->{status} != 200; die("GET on $url did not return any programs to check") if !$response->{content}; my $items = parse_server_response( response => $response->{content} ); die "Failed to parse server requested programs: $response->{content}" if !scalar keys %$items; my $versions = get_versions( items => $items, instances => $instances, ); die "Failed to get any program versions; should have at least gotten Perl" if !scalar keys %$versions; my $client_content = encode_client_response( items => $items, versions => $versions, general_id => md5_hex( hostname() ), ); my $client_response = { headers => { "X-Percona-Toolkit-Tool" => File::Basename::basename($0) }, content => $client_content, }; PTDEBUG && _d('Client response:', Dumper($client_response)); $response = $ua->request('POST', $url, $client_response); PTDEBUG && _d('Server suggestions:', Dumper($response)); die "No response from POST $url $client_response" if !$response; die "POST $url returned HTTP status $response->{status}; expected 200" if $response->{status} != 200; return unless $response->{content}; $items = parse_server_response( response => $response->{content}, split_vars => 0, ); die "Failed to parse server suggestions: $response->{content}" if !scalar keys %$items; my @suggestions = map { $_->{vars} } sort { $a->{item} cmp $b->{item} } values %$items; return \@suggestions; } sub encode_client_response { my (%args) = @_; my @required_args = qw(items versions general_id); foreach my $arg ( @required_args ) { die "I need a $arg arugment" unless $args{$arg}; } my ($items, $versions, $general_id) = @args{@required_args}; my @lines; foreach my $item ( sort keys %$items ) { next unless exists $versions->{$item}; if ( ref($versions->{$item}) eq 'HASH' ) { my $mysql_versions = $versions->{$item}; for my $id ( sort keys %$mysql_versions ) { push @lines, join(';', $id, $item, $mysql_versions->{$id}); } } else { push @lines, join(';', $general_id, $item, $versions->{$item}); } } my $client_response = join("\n", @lines) . "\n"; return $client_response; } sub parse_server_response { my (%args) = @_; my @required_args = qw(response); foreach my $arg ( @required_args ) { die "I need a $arg arugment" unless $args{$arg}; } my ($response) = @args{@required_args}; my %items = map { my ($item, $type, $vars) = split(";", $_); if ( !defined $args{split_vars} || $args{split_vars} ) { $vars = [ split(",", ($vars || '')) ]; } $item => { item => $item, type => $type, vars => $vars, }; } split("\n", $response); PTDEBUG && _d('Items:', Dumper(\%items)); return \%items; } my %sub_for_type = ( os_version => \&get_os_version, perl_version => \&get_perl_version, perl_module_version => \&get_perl_module_version, mysql_variable => \&get_mysql_variable, ); sub valid_item { my ($item) = @_; return unless $item; if ( !exists $sub_for_type{ $item->{type} } ) { PTDEBUG && _d('Invalid type:', $item->{type}); return 0; } return 1; } sub get_versions { my (%args) = @_; my @required_args = qw(items); foreach my $arg ( @required_args ) { die "I need a $arg arugment" unless $args{$arg}; } my ($items) = @args{@required_args}; my %versions; foreach my $item ( values %$items ) { next unless valid_item($item); eval { my $version = $sub_for_type{ $item->{type} }->( item => $item, instances => $args{instances}, ); if ( $version ) { chomp $version unless ref($version); $versions{$item->{item}} = $version; } }; if ( $EVAL_ERROR ) { PTDEBUG && _d('Error getting version for', Dumper($item), $EVAL_ERROR); } } return \%versions; } sub get_os_version { if ( $OSNAME eq 'MSWin32' ) { require Win32; return Win32::GetOSDisplayName(); } chomp(my $platform = `uname -s`); PTDEBUG && _d('platform:', $platform); return $OSNAME unless $platform; chomp(my $lsb_release = `which lsb_release 2>/dev/null | awk '{print \$1}'` || ''); PTDEBUG && _d('lsb_release:', $lsb_release); my $release = ""; if ( $platform eq 'Linux' ) { if ( -f "/etc/fedora-release" ) { $release = `cat /etc/fedora-release`; } elsif ( -f "/etc/redhat-release" ) { $release = `cat /etc/redhat-release`; } elsif ( -f "/etc/system-release" ) { $release = `cat /etc/system-release`; } elsif ( $lsb_release ) { $release = `$lsb_release -ds`; } elsif ( -f "/etc/lsb-release" ) { $release = `grep DISTRIB_DESCRIPTION /etc/lsb-release`; $release =~ s/^\w+="([^"]+)".+/$1/; } elsif ( -f "/etc/debian_version" ) { chomp(my $rel = `cat /etc/debian_version`); $release = "Debian $rel"; if ( -f "/etc/apt/sources.list" ) { chomp(my $code_name = `awk '/^deb/ {print \$3}' /etc/apt/sources.list | awk -F/ '{print \$1}'| awk 'BEGIN {FS="|"} {print \$1}' | sort | uniq -c | sort -rn | head -n1 | awk '{print \$2}'`); $release .= " ($code_name)" if $code_name; } } elsif ( -f "/etc/os-release" ) { # openSUSE chomp($release = `grep PRETTY_NAME /etc/os-release`); $release =~ s/^PRETTY_NAME="(.+)"$/$1/; } elsif ( `ls /etc/*release 2>/dev/null` ) { if ( `grep DISTRIB_DESCRIPTION /etc/*release 2>/dev/null` ) { $release = `grep DISTRIB_DESCRIPTION /etc/*release | head -n1`; } else { $release = `cat /etc/*release | head -n1`; } } } elsif ( $platform =~ m/(?:BSD|^Darwin)$/ ) { my $rel = `uname -r`; $release = "$platform $rel"; } elsif ( $platform eq "SunOS" ) { my $rel = `head -n1 /etc/release` || `uname -r`; $release = "$platform $rel"; } if ( !$release ) { PTDEBUG && _d('Failed to get the release, using platform'); $release = $platform; } chomp($release); $release =~ s/^"|"$//g; PTDEBUG && _d('OS version =', $release); return $release; } sub get_perl_version { my (%args) = @_; my $item = $args{item}; return unless $item; my $version = sprintf '%vd', $PERL_VERSION; PTDEBUG && _d('Perl version', $version); return $version; } sub get_perl_module_version { my (%args) = @_; my $item = $args{item}; return unless $item; my $var = '$' . $item->{item} . '::VERSION'; my $version = eval "use $item->{item}; $var;"; PTDEBUG && _d('Perl version for', $var, '=', $version); return $version; } sub get_mysql_variable { return get_from_mysql( show => 'VARIABLES', @_, ); } sub get_from_mysql { my (%args) = @_; my $show = $args{show}; my $item = $args{item}; my $instances = $args{instances}; return unless $show && $item; if ( !$instances || !@$instances ) { PTDEBUG && _d('Cannot check', $item, 'because there are no MySQL instances'); return; } if ($item->{item} eq 'MySQL' && $item->{type} eq 'mysql_variable') { @{$item->{vars}} = grep { $_ eq 'version' || $_ eq 'version_comment' } @{$item->{vars}}; } my @versions; my %version_for; foreach my $instance ( @$instances ) { next unless $instance->{id}; # special system instance has id=0 my $dbh = $instance->{dbh}; local $dbh->{FetchHashKeyName} = 'NAME_lc'; my $sql = qq/SHOW $show/; PTDEBUG && _d($sql); my $rows = $dbh->selectall_hashref($sql, 'variable_name'); my @versions; foreach my $var ( @{$item->{vars}} ) { $var = lc($var); my $version = $rows->{$var}->{value}; PTDEBUG && _d('MySQL version for', $item->{item}, '=', $version, 'on', $instance->{name}); push @versions, $version; } $version_for{ $instance->{id} } = join(' ', @versions); } return \%version_for; } sub _d { my ($package, undef, $line) = caller 0; @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; } map { defined $_ ? $_ : 'undef' } @_; print STDERR "# $package:$line $PID ", join(' ', @_), "\n"; } 1; } # ########################################################################### # End VersionCheck package # ########################################################################### # ########################################################################### # This is a combination of modules and programs in one -- a runnable module. # http://www.perl.com/pub/a/2006/07/13/lightning-articles.html?page=last # Or, look it up in the Camel book on pages 642 and 643 in the 3rd edition. # # Check at the end of this package for the call to main() which actually runs # the program. # ########################################################################### package pt_find; use strict; use warnings FATAL => 'all'; use English qw(-no_match_vars); use Percona::Toolkit; use constant PTDEBUG => $ENV{PTDEBUG} || 0; $OUTPUT_AUTOFLUSH = 1; # ############################################################################ # Lookup tables and global variables # ############################################################################ my $o; # OptionParser obj my %fmt_for; # Interpolated strings my %time_for; # Holds time constants for mmin, mtime etc my %connections; # Holds a list of thread IDs connected my $server_id; # Holds the server's @@SERVER_ID my $dbh; # This program's $dbh my $exec_dbh; # The $dbh to use for exec and exec-plus my $tp; # Functions to call while evaluating tests. my %test_for = ( autoinc => sub { my ( $table ) = @_; return test_number($table, 'Auto_increment', $o->get('autoinc')); }, avgrowlen => sub { my ( $table ) = @_; return test_number($table, 'Avg_row_length', $o->get('avgrowlen')); }, checksum => sub { my ( $table ) = @_; return test_number($table, 'Checksum', $o->get('checksum')); }, cmin => sub { my ( $table ) = @_; return test_date($table, 'Create_time', 'cmin'); }, collation => sub { my ( $table ) = @_; return test_regex($table, 'Collation', $o->get('collation')); }, 'column-name' => sub { my ( $table ) = @_; my $struct = $table->{struct}; return unless $struct; my $test = $o->get('column-name'); if ( $o->get('case-insensitive') ) { $test = "(?i)$test"; } foreach my $col ( @{$struct->{cols}} ) { return 1 if $col =~ m/$test/; } return 0; }, 'column-type' => sub { my ( $table ) = @_; my $struct = $table->{struct}; return unless $struct; my $test = lc($o->get('column-type')); my $type_for = $struct->{type_for}; foreach my $col ( keys %$type_for ) { return 1 if $type_for->{$col} eq $test; } return 0; }, comment => sub { my ( $table ) = @_; return test_regex($table, 'Comment', $o->get('comment')); }, createopts => sub { my ( $table ) = @_; return test_regex($table, 'Create_options', $o->get('createopts')); }, ctime => sub { my ( $table ) = @_; return test_date($table, 'Create_time', 'ctime'); }, datafree => sub { my ( $table ) = @_; return test_number($table, 'Data_free', $o->get('datafree')); }, datasize => sub { my ( $table ) = @_; return test_number($table, 'Data_length', $o->get('datasize')); }, dbregex => sub { my ( $table ) = @_; return test_regex($table, 'Database', $o->get('dbregex')); }, empty => sub { my ( $table ) = @_; return test_number($table, 'Rows', '0'); }, engine => sub { my ( $table ) = @_; return test_regex($table, 'Engine', $o->get('engine')); }, function => sub { my ( $table ) = @_; return unless $table->{stored_code} && $table->{stored_code} eq 'FUNCTION'; my $def = $table->{def}; return unless $def; my $test = $o->get('function'); if ( $o->get('case-insensitive') ) { $test = "(?i)$test"; } return $def =~ m/$test/; }, indexsize => sub { my ( $table ) = @_; return test_number($table, 'Index_length', $o->get('indexsize')); }, kmin => sub { my ( $table ) = @_; return test_date($table, 'Check_time', 'kmin'); }, ktime => sub { my ( $table ) = @_; return test_date($table, 'Check_time', 'ktime'); }, mmin => sub { my ( $table ) = @_; return test_date($table, 'Update_time', 'mmin'); }, mtime => sub { my ( $table ) = @_; return test_date($table, 'Update_time', 'mtime'); }, 'connection-id' => sub { my ( $table ) = @_; my $test = $o->get('case-insensitive') ? "(?i)".$o->get('connection-id') : $o->get('connection-id'); my ( $pid ) = $table->{Name} =~ m/$test/; return $pid && !exists $connections{$pid}; }, procedure => sub { my ( $table ) = @_; return unless $table->{stored_code} && $table->{stored_code} eq 'PROCEDURE'; my $def = $table->{def}; return unless $def; my $test = $o->get('procedure'); if ( $o->get('case-insensitive') ) { $test = "(?i)$test"; } return $def =~ m/$test/; }, rows => sub { my ( $table ) = @_; return test_number($table, 'Rows', $o->get('rows')); }, rowformat => sub { my ( $table ) = @_; return test_regex($table, 'Row_format', $o->get('rowformat')); }, 'server-id' => sub { my ( $table ) = @_; my $test = $o->get('case-insensitive') ? "(?i)".$o->get('server-id') : $o->get('server-id'); my ( $sid ) = $table->{Name} =~ m/$test/; return $sid && $sid == $server_id; }, tablesize => sub { my ( $table ) = @_; return test_number($table, 'Table_length', $o->get('tablesize')); }, tblregex => sub { my ( $table ) = @_; return test_regex($table, 'Name', $o->get('tblregex')); }, tblversion => sub { my ( $table ) = @_; return test_number($table, 'Version', $o->get('tblversion')); }, trigger => sub { my ( $table ) = @_; return unless $table->{stored_code} && $table->{stored_code} eq 'TRIGGER'; my $def = $table->{def}; return unless $def; my $test = $o->get('trigger'); if ( $o->get('case-insensitive') ) { $test = "(?i)$test"; } return $def =~ m/$test/; }, 'trigger-table' => sub { my ( $table ) = @_; return unless $table->{stored_code} && $table->{stored_code} eq 'TRIGGER'; my $test = $o->get('trigger-table'); if ( $o->get('case-insensitive') ) { $test = "(?i)$test"; } return $table->{trigger_table} =~ m/$test/; }, view => sub { my ( $table ) = @_; my $view = $table->{view}; return unless $view; my $test = $o->get('view'); if ( $o->get('case-insensitive') ) { $test = "(?i)$test"; } return $view =~ m/$test/; }, ); # Functions to call when doing actions my %action_for = ( print => sub { my ( $table ) = @_; print "$table->{Database}.$table->{Name}\n"; }, exec => sub { my ( $table ) = @_; my $sql = sprintf($fmt_for{exec}->{str}, map { defined $_ ? $_ : '' } @{$table}{@{$fmt_for{exec}->{arg_names}}}); PTDEBUG && _d($sql); $exec_dbh->do($sql); }, printf => sub { my ( $table ) = @_; printf($fmt_for{printf}->{str}, map { defined $_ ? $_ : '' } @{$table}{@{$fmt_for{printf}->{arg_names}}}); }, ); my %arg_for = ( a => 'Auto_increment', A => 'Avg_row_length', c => 'Checksum', C => 'Create_time', D => 'Database', d => 'Data_length', E => 'Engine', F => 'Data_free', f => 'Innodb_free', I => 'Index_length', K => 'Check_time', L => 'Collation', M => 'Max_data_length', N => 'Name', O => 'Comment', P => 'Create_options', R => 'Row_format', S => 'Rows', T => 'Table_length', U => 'Update_time', V => 'Version', ); my @table_struct_tests = qw( column-name column-type view ); my @stored_code_tests = qw( procedure function trigger ); sub main { local @ARGV = @_; # set global ARGV for this package # ######################################################################## # Get configuration information. # ######################################################################## my $q = new Quoter(); $o = new OptionParser(); $o->get_specs(); $o->get_opts(); my $dp = $o->DSNParser(); $dp->prop('set-vars', $o->set_vars()); # Make sure OptionParser understands that these options are used. # cmin ctime empty kmin ktime mmin mtime exec printf # Ensure there is a capture group. if ( $o->get('connection-id') && $o->get('connection-id') !~ m/\(\\d\+\)/ ) { $o->save_error("--connection-id regex doesn't capture digits with (\\d+)"); } # Ensure there is a capture group. if ( $o->get('server-id') && $o->get('server-id') !~ m/\(\\d\+\)/ ) { $o->save_error("--server-id regex doesn't capture digits with (\\d+)"); } $o->usage_or_errors(); # Interpolate strings for printf and exec. At the same time discover whether # I must use SHOW TABLE STATUS (slower than SHOW TABLES) to fetch data. my $showstat = grep { $o->get($_) } qw( autoinc avgrowlen checksum cmin collation comment createopts ctime datasize datafree empty engine indexsize kmin ktime mmin mtime rows rowformat tablesize tblversion); foreach my $thing (qw(exec printf)) { next unless $o->get($thing); my ($str, $arg_names) = interpolate($o->get($thing)); $fmt_for{$thing} = { str => $str, arg_names => $arg_names }; if ( grep { $_ !~ m/^(Database|Name)$/ } @$arg_names ) { $showstat = 1; } } # Discover if we need to parse SHOW CREATE TABLE. my $need_table_struct = grep { $o->got($_); } @table_struct_tests; PTDEBUG && _d('Need table struct:', $need_table_struct); if ( $need_table_struct ) { $tp = new TableParser(Quoter => $q); } # ######################################################################## # If --pid, check it first since we'll die if it already exits. # ######################################################################## my $daemon; if ( $o->get('pid') ) { # We're not daemoninzing, it just handles PID stuff. Keep $daemon # in the the scope of main() because when it's destroyed it automatically # removes the PID file. $daemon = new Daemon(o=>$o); $daemon->make_PID_file(); } # ######################################################################## # Get ready to do the main work. # ######################################################################## # Connect to the database. if ( $o->get('ask-pass') ) { $o->set('password', OptionParser::prompt_noecho("Enter password: ")); } my $dsn = $dp->parse_options($o); $dbh = $dp->get_dbh($dp->get_cxn_params($dsn), { AutoCommit => 1 } ); if ( $o->get('exec-dsn') ) { my $exec_dsn = $dp->parse($o->get('exec-dsn'), $dsn); $exec_dbh = $dp->get_dbh($dp->get_cxn_params($exec_dsn), { AutoCommit => 1 }); } else { $exec_dbh = $dbh; } # If no other action was given, the default action is to print. if ( !grep { $o->get($_) } qw( exec exec-plus print printf ) ) { $o->set('print', 1); } # Figure out the time referred to by date/time options. my $basetime; foreach my $option ( grep { defined $o->get($_) } qw(cmin ctime kmin ktime mmin mtime) ) { # Initialize a consistent point in time. $basetime ||= $dbh->selectcol_arrayref( "SELECT " . ($o->get('day-start') ? 'CURRENT_DATE' : 'CURRENT_TIMESTAMP') )->[0]; my ($val) = $o->get($option) =~ m/(\d+)/; my $inter = $option =~ m/min/ ? 'MINUTE' : 'DAY'; my $query = "SELECT DATE_SUB('$basetime', INTERVAL $val $inter)"; $time_for{$option} = $dbh->selectcol_arrayref($query)->[0]; } # Fetch and save a list of processes currently running. if ( $o->get('connection-id') ) { # Ensure I have the PROCESS privilege. my $proc = grep { m/ALL PRIVILEGES.*?\*\.\*|PROCESS/ } @{$dbh->selectcol_arrayref('SHOW GRANTS')}; if ( !$proc ) { die "--connection-id requires the PROCESS privilege for safety.\n"; } } ($server_id) = $dbh->selectrow_array('SELECT @@SERVER_ID'); # Discover if we need to get stored code. Need dbh to do this. my $need_stored_code = grep { $o->got($_); } @stored_code_tests; # ######################################################################## # Do the version-check # ######################################################################## if ( $o->get('version-check') && (!$o->has('quiet') || !$o->get('quiet')) ) { VersionCheck::version_check( force => $o->got('version-check'), instances => [ { dbh => $dbh, dsn => $dsn } ], ); } # ######################################################################## # Go do it. # ######################################################################## my @databases = @ARGV ? @ARGV : $o->get('dblike') ? @{$dbh->selectcol_arrayref('SHOW DATABASES LIKE ?', {}, $o->get('dblike'))} : @{$dbh->selectcol_arrayref('SHOW DATABASES')}; my @exec_plus; DATABASE: foreach my $database ( @databases ) { next DATABASE if $database =~ m/^(?:information_schema|lost\+found)$/mi; my $sta = $showstat ? ' STATUS' : 'S'; my $sth = $o->get('tbllike') ? $dbh->prepare("SHOW TABLE$sta FROM `$database` LIKE ?") : $dbh->prepare("SHOW TABLE$sta FROM `$database`"); $sth->execute($o->get('tbllike') || ()); my @tables = @{$sth->fetchall_arrayref({})}; # Must re-fetch every time; there are too many ways things can go wrong # otherwise (for example, the counter wraps over the unsigned int # boundary). if ( $o->get('connection-id') ) { %connections = map { $_ => 1 } @{$dbh->selectcol_arrayref('SHOW FULL PROCESSLIST')}; } # Make results uniform across MySQL versions, and generate additional # properties. foreach my $table ( @tables ) { if ( $showstat ) { my ($ib_free) = $table->{Comment} && $table->{Comment} =~ m/InnoDB free: (\d+) kB/; $table->{Engine} ||= $table->{Type}; $table->{Table_length} = ($table->{Index_length} || 0) + ($table->{Data_length} || 0); $table->{Innodb_free} = $ib_free ? 1_024 * $ib_free : undef; delete $table->{Type}; } else { my ($name) = values %$table; $table = { Name => $name }; } $table->{Database} = $database; if ( $need_table_struct ) { PTDEBUG && _d('Getting table struct for', $database, '.', $table->{Name}); my $ddl = $tp->get_create_table($dbh, $database, $table->{Name}); if ( $ddl =~ m/CREATE TABLE/ ) { my $table_struct; eval { $table_struct = $tp->parse($ddl) }; if ( $EVAL_ERROR ) { PTDEBUG && _d('Failed to parse table:', $EVAL_ERROR); } $table->{struct} = $table_struct; } else { $table->{view} = $ddl; } } } if ( $need_stored_code ) { foreach my $type ( qw(PROCEDURE FUNCTION) ) { my $sql = "SELECT ROUTINE_NAME AS name, " . " ROUTINE_DEFINITION AS definition " . " FROM INFORMATION_SCHEMA.ROUTINES " . " WHERE ROUTINE_SCHEMA = '$database' " . " AND ROUTINE_TYPE = '$type'"; PTDEBUG && _d($sql); my $codes = $dbh->selectall_arrayref($sql); foreach my $code ( @$codes ) { push @tables, { Database => $database, Name => "$type $code->[0]", stored_code => $type, def => $code->[1], }; } } my $sql = "SELECT TRIGGER_NAME AS name, " . " ACTION_STATEMENT AS action, " . " EVENT_OBJECT_TABLE AS `table`, " . " EVENT_MANIPULATION AS type " . " FROM INFORMATION_SCHEMA.TRIGGERS " . " WHERE EVENT_OBJECT_SCHEMA = '$database'"; PTDEBUG && _d($sql); my $trigs = $dbh->selectall_arrayref($sql); my $codes = $dbh->selectall_arrayref($sql); foreach my $trig ( @$trigs ) { push @tables, { Database => $database, Name => "$trig->[3] TRIGGER $trig->[0] on $trig->[2]", trigger_table => $trig->[2], stored_code => 'TRIGGER', def => $trig->[1], }; } } # Apply the tests to find the matching tables. @tables = grep { my $table = $_; my @tests = grep { $o->get($_) } keys %test_for; if ( @tests ) { ($o->get('or') ? any($table, @tests) : all($table, @tests)); } else { $table; # No tests == all tables (issue 549). } } @tables; # Quote database and table names if desired. if ( $o->get('quote') ) { foreach my $table ( @tables ) { $table->{Database} = $q->quote($table->{Database}); $table->{Name} = $q->quote($table->{Name}); } } foreach my $table ( @tables ) { my @actions = grep { $o->get($_) } keys %action_for; foreach my $action ( @actions ) { $action_for{$action}->($table); } } push @exec_plus, @tables; } # Handle exec-plus. if ( $o->get('exec-plus') ) { my $table_list = join(', ',map {"$_->{Database}.$_->{Name}"} @exec_plus); (my $sql = $o->get('exec-plus')) =~ s/%s/$table_list/g; $exec_dbh->do($sql); } return 0; } # ############################################################################ # Subroutines # ############################################################################ # One test is true sub any { my ( $table, @tests ) = @_; foreach my $test ( @tests ) { return 1 if $test_for{$test}->($table); } return 0; } # All tests are true sub all { my ( $table, @tests ) = @_; foreach my $test ( @tests ) { return 0 unless $test_for{$test}->($table); } return 1; } # Checks the given property of the given table to see if it passes the test sub test_number { my ( $table, $prop, $test ) = @_; # E.g. --datasize NULL. if ( $test eq 'null' ) { return !defined $table->{$prop}; } my ($num) = $test =~ m/(\d+)/; return defined $table->{$prop} && ( ( $test =~ m/-/ && $table->{$prop} < $num ) || ( $test =~ m/\+/ && $table->{$prop} > $num ) || ( $table->{$prop} == $num )); } # Checks the given property of the given table to see if it passes the test sub test_date { my ( $table, $prop, $test ) = @_; return defined $table->{$prop} && ( ( $o->get($test) =~ m/-/ && $table->{$prop} gt $time_for{$test} ) || ( $o->get($test) =~ m/\+/ && $table->{$prop} lt $time_for{$test} ) || ( $table->{$prop} eq $time_for{$test} )); } # Checks the given property of the given table to see if it passes the test sub test_regex { my ( $table, $prop, $test ) = @_; if ( $o->get('case-insensitive') ) { $test = "(?i)$test"; } return defined $table->{$prop} && $table->{$prop} =~ m/$test/; } # Does string-interpolation and stuff. Returns the string and a list of the # properties that go into the resulting placeholders. sub interpolate { my ( $str ) = @_; my @arg_names; # Replace % directives $str =~ s/%(.)/(exists $arg_for{$1} && push @arg_names, $arg_for{$1} ) ? '\%s' : "$1"/xge; # Get Perl to interpolate escape sequences $str =~ s/(?<!\\)"/\\"/g; $str = eval qq{"$str"}; return ( $str, \@arg_names ); } sub expand { my ( $test ) = @_; my %factor_for = (k => 1_024, M => 1_048_576, G => 1_073_741_824); my ($pre, $num, $factor) = $test =~ m/([+-])?(\d+)([kMG])?/; if ( $factor ) { $num *= $factor_for{$factor}; } return "$pre$num"; } sub _d { my ($package, undef, $line) = caller 0; @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; } map { defined $_ ? $_ : 'undef' } @_; print STDERR "# $package:$line $PID ", join(' ', @_), "\n"; } # ############################################################################ # Run the program. # ############################################################################ if ( !caller ) { exit main(@ARGV); } 1; # Because this is a module as well as a script. # ############################################################################ # Documentation # ############################################################################ =pod =head1 NAME pt-find - Find MySQL tables and execute actions, like GNU find. =head1 SYNOPSIS Usage: pt-find [OPTIONS] [DATABASES] pt-find searches for MySQL tables and executes actions, like GNU find. The default action is to print the database and table name. Find all tables created more than a day ago, which use the MyISAM engine, and print their names: pt-find --ctime +1 --engine MyISAM Find InnoDB tables and convert them to MyISAM: pt-find --engine InnoDB --exec "ALTER TABLE %D.%N ENGINE=MyISAM" Find tables created by a process that no longer exists, following the name_sid_pid naming convention, and remove them. pt-find --connection-id '\D_\d+_(\d+)$' --server-id '\D_(\d+)_\d+$' --exec-plus "DROP TABLE %s" Find empty tables in the test and junk databases, and delete them: pt-find --empty junk test --exec-plus "DROP TABLE %s" Find tables more than five gigabytes in total size: pt-find --tablesize +5G Find all tables and print their total data and index size, and sort largest tables first (sort is a different program, by the way). pt-find --printf "%T\t%D.%N\n" | sort -rn As above, but this time, insert the data back into the database for posterity: pt-find --noquote --exec "INSERT INTO sysdata.tblsize(db, tbl, size) VALUES('%D', '%N', %T)" =head1 RISKS Percona Toolkit is mature, proven in the real world, and well tested, but all database tools can pose a risk to the system and the database server. Before using this tool, please: =over =item * Read the tool's documentation =item * Review the tool's known L<"BUGS"> =item * Test the tool on a non-production server =item * Backup your production server and verify the backups =back =head1 DESCRIPTION pt-find looks for MySQL tables that pass the tests you specify, and executes the actions you specify. The default action is to print the database and table name to STDOUT. pt-find is simpler than GNU find. It doesn't allow you to specify complicated expressions on the command line. pt-find uses SHOW TABLES when possible, and SHOW TABLE STATUS when needed. =head1 OPTION TYPES There are three types of options: normal options, which determine some behavior or setting; tests, which determine whether a table should be included in the list of tables found; and actions, which do something to the tables pt-find finds. pt-find uses standard Getopt::Long option parsing, so you should use double dashes in front of long option names, unlike GNU find. =head1 OPTIONS This tool accepts additional command-line arguments. Refer to the L<"SYNOPSIS"> and usage information for details. =over =item --ask-pass Prompt for a password when connecting to MySQL. =item --case-insensitive Specifies that all regular expression searches are case-insensitive. =item --charset short form: -A; type: string Default character set. If the value is utf8, sets Perl's binmode on STDOUT to utf8, passes the mysql_enable_utf8 option to DBD::mysql, and runs SET NAMES UTF8 after connecting to MySQL. Any other value sets binmode on STDOUT without the utf8 layer, and runs SET NAMES after connecting to MySQL. =item --config type: Array Read this comma-separated list of config files; if specified, this must be the first option on the command line. =item --database short form: -D; type: string Connect to this database. =item --day-start Measure times (for L<"--mmin">, etc) from the beginning of today rather than from the current time. =item --defaults-file short form: -F; type: string Only read mysql options from the given file. You must give an absolute pathname. =item --help Show help and exit. =item --host short form: -h; type: string Connect to host. =item --or Combine tests with OR, not AND. By default, tests are evaluated as though there were an AND between them. This option switches it to OR. Option parsing is not implemented by pt-find itself, so you cannot specify complicated expressions with parentheses and mixtures of OR and AND. =item --password short form: -p; type: string Password to use when connecting. If password contains commas they must be escaped with a backslash: "exam\,ple" =item --pid type: string Create the given PID file. The tool won't start if the PID file already exists and the PID it contains is different than the current PID. However, if the PID file exists and the PID it contains is no longer running, the tool will overwrite the PID file with the current PID. The PID file is removed automatically when the tool exits. =item --port short form: -P; type: int Port number to use for connection. =item --[no]quote default: yes Quotes MySQL identifier names with MySQL's standard backtick character. Quoting happens after tests are run, and before actions are run. =item --set-vars type: Array Set the MySQL variables in this comma-separated list of C<variable=value> pairs. By default, the tool sets: =for comment ignore-pt-internal-value MAGIC_set_vars wait_timeout=10000 Variables specified on the command line override these defaults. For example, specifying C<--set-vars wait_timeout=500> overrides the defaultvalue of C<10000>. The tool prints a warning and continues if a variable cannot be set. =item --socket short form: -S; type: string Socket file to use for connection. =item --user short form: -u; type: string User for login if not current user. =item --version Show version and exit. =item --[no]version-check default: yes Check for the latest version of Percona Toolkit, MySQL, and other programs. This is a standard "check for updates automatically" feature, with two additional features. First, the tool checks the version of other programs on the local system in addition to its own version. For example, it checks the version of every MySQL server it connects to, Perl, and the Perl module DBD::mysql. Second, it checks for and warns about versions with known problems. For example, MySQL 5.5.25 had a critical bug and was re-released as 5.5.25a. Any updates or known problems are printed to STDOUT before the tool's normal output. This feature should never interfere with the normal operation of the tool. For more information, visit L<https://www.percona.com/version-check>. =back =head2 TESTS Most tests check some criterion against a column of SHOW TABLE STATUS output. Numeric arguments can be specified as +n for greater than n, -n for less than n, and n for exactly n. All numeric options can take an optional suffix multiplier of k, M or G (1_024, 1_048_576, and 1_073_741_824 respectively). All patterns are Perl regular expressions (see 'man perlre') unless specified as SQL LIKE patterns. Dates and times are all measured relative to the same instant, when pt-find first asks the database server what time it is. All date and time manipulation is done in SQL, so if you say to find tables modified 5 days ago, that translates to SELECT DATE_SUB(CURRENT_TIMESTAMP, INTERVAL 5 DAY). If you specify L<"--day-start">, if course it's relative to CURRENT_DATE instead. However, table sizes and other metrics are not consistent at an instant in time. It can take some time for MySQL to process all the SHOW queries, and pt-find can't do anything about that. These measurements are as of the time they're taken. If you need some test that's not in this list, file a bug report and I'll enhance pt-find for you. It's really easy. =over =item --autoinc type: string; group: Tests Table's next AUTO_INCREMENT is n. This tests the Auto_increment column. =item --avgrowlen type: size; group: Tests Table avg row len is n bytes. This tests the Avg_row_length column. The specified size can be "NULL" to test where Avg_row_length IS NULL. =item --checksum type: string; group: Tests Table checksum is n. This tests the Checksum column. =item --cmin type: size; group: Tests Table was created n minutes ago. This tests the Create_time column. =item --collation type: string; group: Tests Table collation matches pattern. This tests the Collation column. =item --column-name type: string; group: Tests A column name in the table matches pattern. =item --column-type type: string; group: Tests A column in the table matches this type (case-insensitive). Examples of types are: varchar, char, int, smallint, bigint, decimal, year, timestamp, text, enum. =item --comment type: string; group: Tests Table comment matches pattern. This tests the Comment column. =item --connection-id type: string; group: Tests Table name has nonexistent MySQL connection ID. This tests the table name for a pattern. The argument to this test must be a Perl regular expression that captures digits like this: (\d+). If the table name matches the pattern, these captured digits are taken to be the MySQL connection ID of some process. If the connection doesn't exist according to SHOW FULL PROCESSLIST, the test returns true. If the connection ID is greater than pt-find's own connection ID, the test returns false for safety. Why would you want to do this? If you use MySQL statement-based replication, you probably know the trouble temporary tables can cause. You might choose to work around this by creating real tables with unique names, instead of temporary tables. One way to do this is to append your connection ID to the end of the table, thusly: scratch_table_12345. This assures the table name is unique and lets you have a way to find which connection it was associated with. And perhaps most importantly, if the connection no longer exists, you can assume the connection died without cleaning up its tables, and this table is a candidate for removal. This is how I manage scratch tables, and that's why I included this test in pt-find. The argument I use to L<"--connection-id"> is "\D_(\d+)$". That finds tables with a series of numbers at the end, preceded by an underscore and some non-number character (the latter criterion prevents me from examining tables with a date at the end, which people tend to do: baron_scratch_2007_05_07 for example). It's better to keep the scratch tables separate of course. If you do this, make sure the user pt-find runs as has the PROCESS privilege! Otherwise it will only see connections from the same user, and might think some tables are ready to remove when they're still in use. For safety, pt-find checks this for you. See also L<"--server-id">. =item --createopts type: string; group: Tests Table create option matches pattern. This tests the Create_options column. =item --ctime type: size; group: Tests Table was created n days ago. This tests the Create_time column. =item --datafree type: size; group: Tests Table has n bytes of free space. This tests the Data_free column. The specified size can be "NULL" to test where Data_free IS NULL. =item --datasize type: size; group: Tests Table data uses n bytes of space. This tests the Data_length column. The specified size can be "NULL" to test where Data_length IS NULL. =item --dblike type: string; group: Tests Database name matches SQL LIKE pattern. =item --dbregex type: string; group: Tests Database name matches this pattern. =item --empty group: Tests Table has no rows. This tests the Rows column. =item --engine type: string; group: Tests Table storage engine matches this pattern. This tests the Engine column, or in earlier versions of MySQL, the Type column. =item --function type: string; group: Tests Function definition matches pattern. =item --indexsize type: size; group: Tests Table indexes use n bytes of space. This tests the Index_length column. The specified size can be "NULL" to test where Index_length IS NULL. =item --kmin type: size; group: Tests Table was checked n minutes ago. This tests the Check_time column. =item --ktime type: size; group: Tests Table was checked n days ago. This tests the Check_time column. =item --mmin type: size; group: Tests Table was last modified n minutes ago. This tests the Update_time column. =item --mtime type: size; group: Tests Table was last modified n days ago. This tests the Update_time column. =item --procedure type: string; group: Tests Procedure definition matches pattern. =item --rowformat type: string; group: Tests Table row format matches pattern. This tests the Row_format column. =item --rows type: size; group: Tests Table has n rows. This tests the Rows column. The specified size can be "NULL" to test where Rows IS NULL. =item --server-id type: string; group: Tests Table name contains the server ID. If you create temporary tables with the naming convention explained in L<"--connection-id">, but also add the server ID of the server on which the tables are created, then you can use this pattern match to ensure tables are dropped only on the server they're created on. This prevents a table from being accidentally dropped on a slave while it's in use (provided that your server IDs are all unique, which they should be for replication to work). For example, on the master (server ID 22) you create a table called scratch_table_22_12345. If you see this table on the slave (server ID 23), you might think it can be dropped safely if there's no such connection 12345. But if you also force the name to match the server ID with C<--server-id '\D_(\d+)_\d+$'>, the table won't be dropped on the slave. =item --tablesize type: size; group: Tests Table uses n bytes of space. This tests the sum of the Data_length and Index_length columns. =item --tbllike type: string; group: Tests Table name matches SQL LIKE pattern. =item --tblregex type: string; group: Tests Table name matches this pattern. =item --tblversion type: size; group: Tests Table version is n. This tests the Version column. =item --trigger type: string; group: Tests Trigger action statement matches pattern. =item --trigger-table type: string; group: Tests L<"--trigger"> is defined on table matching pattern. =item --view type: string; group: Tests CREATE VIEW matches this pattern. =back =head2 ACTIONS The L<"--exec-plus"> action happens after everything else, but otherwise actions happen in an indeterminate order. If you need determinism, file a bug report and I'll add this feature. =over =item --exec type: string; group: Actions Execute this SQL with each item found. The SQL can contain escapes and formatting directives (see L<"--printf">). =item --exec-dsn type: string; group: Actions Specify a DSN in key-value format to use when executing SQL with L<"--exec"> and L<"--exec-plus">. Any values not specified are inherited from command-line arguments. =item --exec-plus type: string; group: Actions Execute this SQL with all items at once. This option is unlike L<"--exec">. There are no escaping or formatting directives; there is only one special placeholder for the list of database and table names, %s. The list of tables found will be joined together with commas and substituted wherever you place %s. You might use this, for example, to drop all the tables you found: DROP TABLE %s This is sort of like GNU find's "-exec command {} +" syntax. Only it's not totally cryptic. And it doesn't require me to write a command-line parser. =item --print group: Actions Print the database and table name, followed by a newline. This is the default action if no other action is specified. =item --printf type: string; group: Actions Print format on the standard output, interpreting '\' escapes and '%' directives. Escapes are backslashed characters, like \n and \t. Perl interprets these, so you can use any escapes Perl knows about. Directives are replaced by %s, and as of this writing, you can't add any special formatting instructions, like field widths or alignment (though I'm musing over ways to do that). Here is a list of the directives. Note that most of them simply come from columns of SHOW TABLE STATUS. If the column is NULL or doesn't exist, you get an empty string in the output. A % character followed by any character not in the following list is discarded (but the other character is printed). CHAR DATA SOURCE NOTES ---- ------------------ ------------------------------------------ a Auto_increment A Avg_row_length c Checksum C Create_time D Database The database name in which the table lives d Data_length E Engine In older versions of MySQL, this is Type F Data_free f Innodb_free Parsed from the Comment field I Index_length K Check_time L Collation M Max_data_length N Name O Comment P Create_options R Row_format S Rows T Table_length Data_length+Index_length U Update_time V Version =back =head1 DSN OPTIONS These DSN options are used to create a DSN. Each option is given like C<option=value>. The options are case-sensitive, so P and p are not the same option. There cannot be whitespace before or after the C<=> and if the value contains whitespace it must be quoted. DSN options are comma-separated. See the L<percona-toolkit> manpage for full details. =over =item * A dsn: charset; copy: yes Default character set. =item * D dsn: database; copy: yes Default database. =item * F dsn: mysql_read_default_file; copy: yes Only read default options from the given file =item * h dsn: host; copy: yes Connect to host. =item * p dsn: password; copy: yes Password to use when connecting. If password contains commas they must be escaped with a backslash: "exam\,ple" =item * P dsn: port; copy: yes Port number to use for connection. =item * S dsn: mysql_socket; copy: yes Socket file to use for connection. =item * u dsn: user; copy: yes User for login if not current user. =back =head1 ENVIRONMENT The environment variable C<PTDEBUG> enables verbose debugging output to STDERR. To enable debugging and capture all output to a file, run the tool like: PTDEBUG=1 pt-find ... > FILE 2>&1 Be careful: debugging output is voluminous and can generate several megabytes of output. =head1 SYSTEM REQUIREMENTS You need Perl, DBI, DBD::mysql, and some core packages that ought to be installed in any reasonably new version of Perl. =head1 BUGS For a list of known bugs, see L<http://www.percona.com/bugs/pt-find>. Please report bugs at L<https://bugs.launchpad.net/percona-toolkit>. Include the following information in your bug report: =over =item * Complete command-line used to run the tool =item * Tool L<"--version"> =item * MySQL version of all servers involved =item * Output from the tool including STDERR =item * Input files (log/dump/config files, etc.) =back If possible, include debugging output by running the tool with C<PTDEBUG>; see L<"ENVIRONMENT">. =head1 DOWNLOADING Visit L<http://www.percona.com/software/percona-toolkit/> to download the latest release of Percona Toolkit. Or, get the latest release from the command line: wget percona.com/get/percona-toolkit.tar.gz wget percona.com/get/percona-toolkit.rpm wget percona.com/get/percona-toolkit.deb You can also get individual tools from the latest release: wget percona.com/get/TOOL Replace C<TOOL> with the name of any tool. =head1 AUTHORS Baron Schwartz =head1 ABOUT PERCONA TOOLKIT This tool is part of Percona Toolkit, a collection of advanced command-line tools for MySQL developed by Percona. Percona Toolkit was forked from two projects in June, 2011: Maatkit and Aspersa. Those projects were created by Baron Schwartz and primarily developed by him and Daniel Nichter. Visit L<http://www.percona.com/software/> to learn about other free, open-source software from Percona. =head1 COPYRIGHT, LICENSE, AND WARRANTY This program is copyright 2011-2015 Percona LLC and/or its affiliates, 2007-2011 Baron Schwartz. THIS PROGRAM IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2; OR the Perl Artistic License. On UNIX and similar systems, you can issue `man perlgpl' or `man perlartistic' to read these licenses. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. =head1 VERSION pt-find 2.2.16 =cut
Close