diff options
author | Andris Zeila <andris.zeila@zabbix.com> | 2022-01-20 13:20:53 +0300 |
---|---|---|
committer | Aleksejs Sestakovs <aleksejs.sestakovs@zabbix.com> | 2022-01-20 13:33:46 +0300 |
commit | c447d96509ae692d1ba79c65a221a81523280287 (patch) | |
tree | 33fed37cec8c780a618f8a71b7b712cbec735ac8 /src/go/plugins | |
parent | 03e25604336eaa8234f5b66c0f3c2fe293b90189 (diff) |
...G...... [ZBXNEXT-7409] added native support for the vfs.dir.size and vfs.dir.count keys in Zabbix agent 2
* commit '8f3eaae228fd3646c87b6d779e526aaf99ce29e8': (26 commits)
...G...... [ZBXNEXT-7409] fixed coding style
...G...... [ZBXNEXT-7409] fixed agent2 vfs.dir.count to trim spaces in types
...G...... [ZBXNEXT-7409] fixed vfs.dir.size to sum hard links once
...G...... [ZBXNEXT-7409] fixed vfs.dir.count and vfs.dir.size for windows
.D........ [ZBXNEXT-7409] fixed changelog entry
.D........ [ZBXNEXT-7409] renamed changelog file
.D........ [ZBXNEXT-7408] added changelog entry
.......... [ZBXNEXT-6875] fixed symlink processing with vfs.dir.* functions
...G...... [ZBXNEXT-6875] removed unused function parameter
...G...... [ZBXNEXT-6875] fixed hard link and symlink interaction on Windows
...G...... [ZBXNEXT-6875] code clean up
...G...... [ZBXNEXT-6875] fixed vfs.dir.size vfs.dir.count stopping on file info fail
...G...... [ZBXNEXT-6875] fixed vfs.dir.count stopping on dir fail
...G...... [ZBXNEXT-6875] fixing sub issues and added changelog
...G...... [ZBXNEXT-6875] updated byte constants
...G...... [ZBXNEXT-6875] fixing sub-issues
...G...... [ZBXNEXT-6875] code clean-up
...G...... [ZBXNEXT-6875] code clean-up
...G...... [ZBXNEXT-6875] code clean-up
...G...... [ZBXNEXT-6875] implemented vfs.dir.size in Zabbix agent 2 for Windows
...
(cherry picked from commit 577e78291bfff5120f13d6e0f79c1654e6906d92)
(cherry picked from commit d8edaf13526317a33148934b24423dce1b5d1017)
Diffstat (limited to 'src/go/plugins')
-rw-r--r-- | src/go/plugins/plugins_linux.go | 1 | ||||
-rw-r--r-- | src/go/plugins/plugins_windows.go | 1 | ||||
-rw-r--r-- | src/go/plugins/vfs/dir/count.go | 487 | ||||
-rw-r--r-- | src/go/plugins/vfs/dir/count_nix.go | 37 | ||||
-rw-r--r-- | src/go/plugins/vfs/dir/count_test.go | 120 | ||||
-rw-r--r-- | src/go/plugins/vfs/dir/count_windows.go | 74 | ||||
-rw-r--r-- | src/go/plugins/vfs/dir/dir.go | 121 | ||||
-rw-r--r-- | src/go/plugins/vfs/dir/size.go | 220 | ||||
-rw-r--r-- | src/go/plugins/vfs/dir/size_nix.go | 73 | ||||
-rw-r--r-- | src/go/plugins/vfs/dir/size_windows.go | 108 | ||||
-rw-r--r-- | src/go/plugins/zabbix/sync/sync_nix.go | 3 | ||||
-rw-r--r-- | src/go/plugins/zabbix/sync/sync_windows.go | 2 |
12 files changed, 1243 insertions, 4 deletions
diff --git a/src/go/plugins/plugins_linux.go b/src/go/plugins/plugins_linux.go index 302e4942ade..33788d49989 100644 --- a/src/go/plugins/plugins_linux.go +++ b/src/go/plugins/plugins_linux.go @@ -47,6 +47,7 @@ import ( _ "zabbix.com/plugins/systemd" _ "zabbix.com/plugins/systemrun" _ "zabbix.com/plugins/vfs/dev" + _ "zabbix.com/plugins/vfs/dir" _ "zabbix.com/plugins/vfs/file" _ "zabbix.com/plugins/vfs/fs" _ "zabbix.com/plugins/vm/memory" diff --git a/src/go/plugins/plugins_windows.go b/src/go/plugins/plugins_windows.go index 09c76f64dae..0c5c15c0dc8 100644 --- a/src/go/plugins/plugins_windows.go +++ b/src/go/plugins/plugins_windows.go @@ -41,6 +41,7 @@ import ( _ "zabbix.com/plugins/system/uptime" _ "zabbix.com/plugins/system/users" _ "zabbix.com/plugins/systemrun" + _ "zabbix.com/plugins/vfs/dir" _ "zabbix.com/plugins/vfs/file" _ "zabbix.com/plugins/vfs/fs" _ "zabbix.com/plugins/vm/memory" diff --git a/src/go/plugins/vfs/dir/count.go b/src/go/plugins/vfs/dir/count.go new file mode 100644 index 00000000000..45438c83c2d --- /dev/null +++ b/src/go/plugins/vfs/dir/count.go @@ -0,0 +1,487 @@ +/* +** Zabbix +** Copyright (C) 2001-2021 Zabbix SIA +** +** 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; either version 2 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +**/ + +package dir + +import ( + "fmt" + "io/fs" + "path/filepath" + "strconv" + "strings" + "time" + + "zabbix.com/pkg/plugin" + + "zabbix.com/pkg/zbxerr" +) + +const ( + emptyParam = iota + firstParam + secondParam + thirdParam + fourthParam + fifthParam + sixthParam + seventhParam + eightParam + ninthParam + tenthParam + eleventhParam + + regularFile = 0 + + unlimitedDepth = -1 + + kilobyteType = 'K' + megabyteType = 'M' + gigabyteType = 'G' + terabyteType = 'T' + + kb = 1024 + mb = kb * 1024 + gb = mb * 1024 + tb = gb * 1024 + + secondsType = 's' + minuteType = 'm' + hourType = 'h' + dayType = 'd' + weekType = 'w' + + dayMultiplier = 24 + weekMultiplier = 7 +) + +//Plugin - +type Plugin struct { + plugin.Base +} + +type countParams struct { + common + minSize string + maxSize string + minAge string + maxAge string + parsedMinSize int64 + parsedMaxSize int64 + parsedMinAge time.Time + parsedMaxAge time.Time + typesInclude map[fs.FileMode]bool + typesExclude map[fs.FileMode]bool +} + +func (cp *countParams) getDirCount() (int, error) { + var count int + + err := filepath.WalkDir(cp.path, + func(p string, d fs.DirEntry, err error) error { + if err != nil { + impl.Logger.Errf("failed to walk dir with path %s", p) + return nil + } + + if p == cp.path { + return nil + } + + s, err := cp.skip(p, d) + if s { + return err + } + + count++ + + return nil + }) + + if err != nil { + return 0, zbxerr.ErrorCannotParseResult.Wrap(err) + } + + return count, nil +} + +func (cp *countParams) skip(path string, d fs.DirEntry) (bool, error) { + var s bool + + s, err := cp.skipPath(path) + if s { + return true, err + } + + s, err = cp.skipRegex(d) + if s { + return true, err + } + + s, err = cp.skipInfo(d) + if s { + if err != nil { + impl.Logger.Errf("failed to get file info for path %s, %s", path, err.Error()) + } + + return true, nil + } + + if cp.skipType(path, d) { + return true, nil + } + + return false, nil +} + +func (cp *countParams) skipInfo(d fs.DirEntry) (bool, error) { + i, err := d.Info() + if err != nil { + return true, err + } + + if cp.minSize != "" { + if cp.parsedMinSize > i.Size() { + return true, nil + } + } + + if cp.maxSize != "" { + if cp.parsedMaxSize < i.Size() { + return true, nil + } + } + + if cp.minAge != "" && i.ModTime().After(cp.parsedMinAge) { + return true, nil + } + + if cp.maxAge != "" && i.ModTime().Before(cp.parsedMaxAge) { + return true, nil + } + + return false, nil +} + +func (cp *countParams) setMinMax() (err error) { + err = cp.setMinParams() + if err != nil { + return + } + + err = cp.setMaxParams() + if err != nil { + return + } + + return +} + +func (cp *countParams) setMaxParams() (err error) { + cp.parsedMaxSize, err = parseByte(cp.maxSize) + if err != nil { + return + } + + if cp.maxAge != "" { + var age time.Duration + age, err = parseTime(cp.maxAge) + if err != nil { + return + } + + cp.parsedMaxAge = time.Now().Add(-age) + } + + return +} + +func (cp *countParams) setMinParams() (err error) { + cp.parsedMinSize, err = parseByte(cp.minSize) + if err != nil { + err = zbxerr.ErrorInvalidParams.Wrap(err) + + return + } + + if cp.minAge != "" { + var age time.Duration + age, err = parseTime(cp.minAge) + if err != nil { + return + } + + cp.parsedMinAge = time.Now().Add(-age) + } + + return +} + +func isTypeMatch(in map[fs.FileMode]bool, fm fs.FileMode) bool { + if in[regularFile] && fm.IsRegular() { + return true + } + + if in[fm.Type()] { + return true + } + + return false +} + +func getCountParams(params []string) (out countParams, err error) { + out.maxDepth = -1 + + switch len(params) { + case eleventhParam: + out.dirRegExclude, err = parseReg(params[10]) + if err != nil { + err = zbxerr.New("Invalid eleventh parameter.").Wrap(err) + + return + } + + fallthrough + case tenthParam: + out.maxAge = params[9] + + fallthrough + case ninthParam: + out.minAge = params[8] + + fallthrough + case eightParam: + out.maxSize = params[7] + + fallthrough + case seventhParam: + out.minSize = params[6] + + fallthrough + case sixthParam: + if params[5] != "" { + out.maxDepth, err = strconv.Atoi(params[5]) + if err != nil { + err = zbxerr.New("Invalid sixth parameter.").Wrap(err) + + return + } + + if out.maxDepth < unlimitedDepth { + err = zbxerr.New("Invalid sixth parameter.") + + return + } + } + + fallthrough + case fifthParam: + out.typesExclude, err = parseType(params[4], true) + if err != nil { + err = zbxerr.New("Invalid fifth parameter.").Wrap(err) + + return + } + + fallthrough + case fourthParam: + out.typesInclude, err = parseType(params[3], false) + if err != nil { + err = zbxerr.New("Invalid fourth parameter.").Wrap(err) + + return + } + + fallthrough + case thirdParam: + out.regExclude, err = parseReg(params[2]) + if err != nil { + err = zbxerr.New("Invalid third parameter.").Wrap(err) + + return + } + + fallthrough + case secondParam: + out.regInclude, err = parseReg(params[1]) + if err != nil { + err = zbxerr.New("Invalid second parameter.").Wrap(err) + + return + } + + fallthrough + case firstParam: + out.path = params[0] + if out.path == "" { + err = zbxerr.New("Invalid first parameter.") + + return + } + + if !strings.HasSuffix(out.path, string(filepath.Separator)) { + out.path += string(filepath.Separator) + } + + case emptyParam: + err = zbxerr.ErrorTooFewParameters + + return + default: + err = zbxerr.ErrorTooManyParameters + + return + } + + return +} + +func parseType(in string, exclude bool) (out map[fs.FileMode]bool, err error) { + if in == "" { + return getEmptyType(exclude), nil + } + + out = make(map[fs.FileMode]bool) + types := strings.Split(in, ",") + + for _, t := range types { + t = strings.TrimSpace(t) + + switch t { + case "all": + //If all are set no need to iterate further + return getAllMode(), nil + default: + out, err = setIndividualType(out, t) + if err != nil { + return nil, err + } + } + } + + return out, nil +} + +func setIndividualType(m map[fs.FileMode]bool, t string) (map[fs.FileMode]bool, error) { + switch t { + case "file": + m[regularFile] = true + case "dir": + m[fs.ModeDir] = true + case "sym": + m[fs.ModeSymlink] = true + case "sock": + m[fs.ModeSocket] = true + case "bdev": + m[fs.ModeDevice] = true + case "cdev": + m[fs.ModeDevice+fs.ModeCharDevice] = true + case "fifo": + m[fs.ModeNamedPipe] = true + case "dev": + m[fs.ModeDevice] = true + m[fs.ModeDevice+fs.ModeCharDevice] = true + default: + return nil, fmt.Errorf("invalid type: %s", t) + } + + return m, nil +} + +func getEmptyType(exclude bool) map[fs.FileMode]bool { + if exclude { + return nil + } + + return getAllMode() +} + +func getAllMode() map[fs.FileMode]bool { + out := make(map[fs.FileMode]bool) + out[regularFile] = true + out[fs.ModeDir] = true + out[fs.ModeSymlink] = true + out[fs.ModeSocket] = true + out[fs.ModeDevice] = true + out[fs.ModeCharDevice+fs.ModeDevice] = true + out[fs.ModeNamedPipe] = true + + return out +} + +func parseByte(in string) (int64, error) { + if in == "" { + return 0, nil + } + + bytes, err := strconv.ParseInt(in, 10, 64) + if err != nil { + bytes, err := strconv.ParseInt(in[:len(in)-1], 10, 64) + if err != nil { + return 0, err + } + + suffix := in[len(in)-1] + switch suffix { + case kilobyteType: + return bytes * kb, nil + case megabyteType: + return bytes * mb, nil + case gigabyteType: + return bytes * gb, nil + case terabyteType: + return bytes * tb, nil + default: + return 0, fmt.Errorf("unknown memory suffix %s", string(suffix)) + } + } + + return bytes, nil +} + +func parseTime(in string) (time.Duration, error) { + if in == "" { + return 0 * time.Second, nil + } + + t, err := strconv.ParseInt(in, 10, 64) + if err != nil { + t, err := strconv.ParseInt(in[:len(in)-1], 10, 64) + if err != nil { + return 0, err + } + + suffix := in[len(in)-1] + switch suffix { + case secondsType: + return time.Duration(t) * time.Second, nil + case minuteType: + return time.Duration(t) * time.Minute, nil + case hourType: + return time.Duration(t) * time.Hour, nil + case dayType: + return time.Duration(t) * time.Hour * dayMultiplier, nil + case weekType: + return time.Duration(t) * time.Hour * dayMultiplier * weekMultiplier, nil + default: + return 0, fmt.Errorf("unknown time suffix %s", string(suffix)) + } + } + + return time.Duration(t) * time.Second, nil +} diff --git a/src/go/plugins/vfs/dir/count_nix.go b/src/go/plugins/vfs/dir/count_nix.go new file mode 100644 index 00000000000..a0fe46c9d2d --- /dev/null +++ b/src/go/plugins/vfs/dir/count_nix.go @@ -0,0 +1,37 @@ +//go:build !windows +// +build !windows + +/* +** Zabbix +** Copyright (C) 2001-2021 Zabbix SIA +** +** 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; either version 2 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +**/ + +package dir + +import "io/fs" + +func (cp *countParams) skipType(path string, d fs.DirEntry) bool { + if len(cp.typesInclude) > 0 && !isTypeMatch(cp.typesInclude, d.Type()) { + return true + } + + if len(cp.typesExclude) > 0 && isTypeMatch(cp.typesExclude, d.Type()) { + return true + } + + return false +} diff --git a/src/go/plugins/vfs/dir/count_test.go b/src/go/plugins/vfs/dir/count_test.go new file mode 100644 index 00000000000..23c6a9f49ab --- /dev/null +++ b/src/go/plugins/vfs/dir/count_test.go @@ -0,0 +1,120 @@ +/* +** Zabbix +** Copyright (C) 2001-2021 Zabbix SIA +** +** 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; either version 2 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +**/ + +package dir + +import ( + "io/fs" + "reflect" + "testing" + "time" +) + +func Test_parseByte(t *testing.T) { + type args struct { + in string + } + tests := []struct { + name string + args args + want int64 + wantErr bool + }{ + {"+byte", args{"1024"}, 1024, false}, + {"+kB", args{"1K"}, 1024, false}, + {"+mB", args{"1M"}, 1024 * 1024, false}, + {"+gB", args{"1G"}, 1024 * 1024 * 1024, false}, + {"+tB", args{"1T"}, 1024 * 1024 * 1024 * 1024, false}, + {"-empty", args{""}, 0, false}, + {"-invalid_string", args{"foobar"}, 0, true}, + {"-invalid_suffix", args{"1A"}, 0, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := parseByte(tt.args.in) + if (err != nil) != tt.wantErr { + t.Errorf("parseByte() error = %v, wantErr %v", err, tt.wantErr) + + return + } + if got != tt.want { + t.Errorf("parseByte() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_parseTime(t *testing.T) { + type args struct { + in string + } + tests := []struct { + name string + args args + want time.Duration + wantErr bool + }{ + {"+second", args{"60"}, time.Minute, false}, + {"+second_suffix", args{"60s"}, time.Minute, false}, + {"+minute", args{"1m"}, time.Minute, false}, + {"+hour", args{"1h"}, time.Hour, false}, + {"+hour", args{"1d"}, time.Hour * dayMultiplier, false}, + {"+hour", args{"1w"}, time.Hour * dayMultiplier * weekMultiplier, false}, + {"-empty", args{""}, 0, false}, + {"-invalid_string", args{"foobar"}, 0, true}, + {"-invalid_suffix", args{"1A"}, 0, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := parseTime(tt.args.in) + if (err != nil) != tt.wantErr { + t.Errorf("parseTime() error = %v, wantErr %v", err, tt.wantErr) + + return + } + if got != tt.want { + t.Errorf("parseTime() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_getAllMode(t *testing.T) { + tests := []struct { + name string + want map[fs.FileMode]bool + }{ + {"new", map[fs.FileMode]bool{ + regularFile: true, + fs.ModeDir: true, + fs.ModeSymlink: true, + fs.ModeSocket: true, + fs.ModeDevice: true, + fs.ModeCharDevice + fs.ModeDevice: true, + fs.ModeNamedPipe: true, + }}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := getAllMode(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("getAllMode() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/src/go/plugins/vfs/dir/count_windows.go b/src/go/plugins/vfs/dir/count_windows.go new file mode 100644 index 00000000000..15343257eb0 --- /dev/null +++ b/src/go/plugins/vfs/dir/count_windows.go @@ -0,0 +1,74 @@ +/* +** Zabbix +** Copyright (C) 2001-2021 Zabbix SIA +** +** 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; either version 2 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +**/ + +package dir + +import ( + "io/fs" + "os" + "syscall" +) + +func (cp *countParams) skipType(path string, d fs.DirEntry) bool { + var typeFile bool + var typeDir bool + + i, err := d.Info() + if err != nil { + impl.Logger.Errf("failed to get file info for path %s, %s", path, err.Error()) + return true + } + + if attr, ok := i.Sys().(*syscall.Win32FileAttributeData); ok { + if (len(cp.typesInclude) > 0 && !isTypeMatch(cp.typesInclude, regularFile)) || + (len(cp.typesExclude) > 0 && isTypeMatch(cp.typesExclude, regularFile)) { + typeFile = false + } else { + typeFile = true + } + + if (len(cp.typesInclude) > 0 && !isTypeMatch(cp.typesInclude, fs.ModeDir)) || + (len(cp.typesExclude) > 0 && isTypeMatch(cp.typesExclude, fs.ModeDir)) { + typeDir = false + } else { + typeDir = true + } + + if attr.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT == 0 { + if attr.FileAttributes&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 && typeDir { + return false + } else if attr.FileAttributes&syscall.FILE_ATTRIBUTE_DIRECTORY == 0 && typeFile { + for _, f := range cp.files { + if os.SameFile(f, i) { + return true + } + } + + cp.common.files = append(cp.common.files, i) + return false + } + } else if attr.FileAttributes&syscall.FILE_ATTRIBUTE_DIRECTORY == 0 && typeFile { + return false + } + } else { + impl.Logger.Errf("failed to get system file attribute data") + } + + return true +} diff --git a/src/go/plugins/vfs/dir/dir.go b/src/go/plugins/vfs/dir/dir.go new file mode 100644 index 00000000000..40901c01d3b --- /dev/null +++ b/src/go/plugins/vfs/dir/dir.go @@ -0,0 +1,121 @@ +/* +** Zabbix +** Copyright (C) 2001-2021 Zabbix SIA +** +** 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; either version 2 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +**/ + +package dir + +import ( + "io/fs" + "path/filepath" + "regexp" + "strings" + + "zabbix.com/pkg/plugin" + "zabbix.com/pkg/zbxerr" +) + +var impl Plugin + +type common struct { + path string + maxDepth int + length int + regExclude *regexp.Regexp + regInclude *regexp.Regexp + dirRegExclude *regexp.Regexp + files []fs.FileInfo +} + +//Export - +func (p *Plugin) Export(key string, params []string, ctx plugin.ContextProvider) (result interface{}, err error) { + switch key { + case "vfs.dir.count": + return p.exportCount(params) + case "vfs.dir.size": + return p.exportSize(params) + default: + return nil, zbxerr.ErrorUnsupportedMetric + } +} + +func (p *Plugin) exportCount(params []string) (result interface{}, err error) { + cp, err := getCountParams(params) + if err != nil { + return + } + + cp.length = len(strings.SplitAfter(cp.path, string(filepath.Separator))) + + err = cp.setMinMax() + if err != nil { + return 0, zbxerr.ErrorInvalidParams.Wrap(err) + } + + return cp.getDirCount() +} + +func (p *Plugin) exportSize(params []string) (result interface{}, err error) { + sp, err := getSizeParams(params) + if err != nil { + return + } + + sp.length = len(strings.SplitAfter(sp.path, string(filepath.Separator))) + + return sp.getDirSize() +} + +func (cp *common) skipPath(path string) (bool, error) { + currentLength := len(strings.SplitAfter(path, string(filepath.Separator))) + if cp.maxDepth > unlimitedDepth && currentLength-cp.length > cp.maxDepth { + return true, fs.SkipDir + } + + return false, nil +} + +func (cp *common) skipRegex(d fs.DirEntry) (bool, error) { + if cp.regInclude != nil && !cp.regInclude.Match([]byte(d.Name())) { + return true, nil + } + + if cp.regExclude != nil && cp.regExclude.Match([]byte(d.Name())) { + return true, nil + } + + if cp.dirRegExclude != nil && d.IsDir() && cp.dirRegExclude.Match([]byte(d.Name())) { + return true, fs.SkipDir + } + + return false, nil +} + +func parseReg(in string) (*regexp.Regexp, error) { + if in == "" { + return nil, nil + } + + return regexp.Compile(in) +} + +func init() { + plugin.RegisterMetrics(&impl, "VFSDir", + "vfs.dir.count", "Directory entry count.", + "vfs.dir.size", "All directory entry size.", + ) +} diff --git a/src/go/plugins/vfs/dir/size.go b/src/go/plugins/vfs/dir/size.go new file mode 100644 index 00000000000..2e69615f062 --- /dev/null +++ b/src/go/plugins/vfs/dir/size.go @@ -0,0 +1,220 @@ +/* +** Zabbix +** Copyright (C) 2001-2021 Zabbix SIA +** +** 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; either version 2 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +**/ + +package dir + +import ( + "io/fs" + "os" + "path/filepath" + "strconv" + "strings" + + "zabbix.com/pkg/zbxerr" +) + +const ( + diskBlockSize = 512 +) + +type sizeParams struct { + common + diskMode bool + disk string +} + +func (sp *sizeParams) getDirSize() (int64, error) { + var parentSize int64 + var dirSize int64 + + err := filepath.WalkDir(sp.path, + func(p string, d fs.DirEntry, err error) error { + if err != nil { + impl.Logger.Errf("failed to walk dir with path %s", p) + return nil + } + + if p == sp.path { + parentSize, err = sp.handleHomeDir(p, d) + if err != nil { + return err + } + + return nil + } + + s, err := sp.skip(p, d) + if s { + return err + } + + fi, err := d.Info() + if err != nil { + impl.Logger.Errf("failed to get file info for path %s, %s", p, err.Error()) + return nil + } + + tmpSize, err := sp.getSize(fi, p) + if err != nil { + return err + } + + dirSize += tmpSize + + return nil + }) + + if err != nil { + return 0, zbxerr.ErrorCannotParseResult.Wrap(err) + } + + return parentSize + dirSize, nil +} + +func (sp *sizeParams) getParentSize(dir fs.DirEntry) (int64, error) { + var parentSize int64 + + stat, err := os.Stat(sp.path) + if err != nil { + return 0, err + } + + s, err := sp.skipRegex(dir) + if err != nil { + return 0, err + } + + if s { + return 0, nil + } + + parentSize, err = sp.getSize(stat, sp.path) + if err != nil { + return 0, err + } + + return parentSize, nil +} + +func (sp *sizeParams) skip(path string, d fs.DirEntry) (bool, error) { + s, err := sp.skipPath(path) + if s { + return true, err + } + + s, err = sp.skipRegex(d) + if s { + return true, err + } + + if skipDir(d) { + return true, nil + } + + if sp.osSkip(path, d) { + return true, nil + } + + return false, nil +} + +func getSizeParams(params []string) (out sizeParams, err error) { + out.maxDepth = -1 + + switch len(params) { + case sixthParam: + out.dirRegExclude, err = parseReg(params[5]) + if err != nil { + err = zbxerr.New("Invalid sixth parameter.").Wrap(err) + + return + } + + fallthrough + case fifthParam: + if params[4] != "" { + out.maxDepth, err = strconv.Atoi(params[4]) + if err != nil { + err = zbxerr.New("Invalid fifth parameter.").Wrap(err) + + return + } + + if out.maxDepth < unlimitedDepth { + err = zbxerr.New("Invalid fifth parameter.") + + return + } + } + + fallthrough + case fourthParam: + switch params[3] { + case "apparent", "": + case "disk": + out.diskMode = true + default: + err = zbxerr.New("Invalid fourth parameter.").Wrap(err) + + return + } + + fallthrough + case thirdParam: + out.regExclude, err = parseReg(params[2]) + if err != nil { + err = zbxerr.New("Invalid third parameter.").Wrap(err) + + return + } + + fallthrough + case secondParam: + out.regInclude, err = parseReg(params[1]) + if err != nil { + err = zbxerr.New("Invalid second parameter.").Wrap(err) + + return + } + + fallthrough + case firstParam: + out.path = params[0] + if out.path == "" { + err = zbxerr.New("Invalid first parameter.") + + return + } + + if !strings.HasSuffix(out.path, string(filepath.Separator)) { + out.path += string(filepath.Separator) + } + + case emptyParam: + err = zbxerr.ErrorTooFewParameters + + return + default: + err = zbxerr.ErrorTooManyParameters + + return + } + + return +} diff --git a/src/go/plugins/vfs/dir/size_nix.go b/src/go/plugins/vfs/dir/size_nix.go new file mode 100644 index 00000000000..343fbb07a33 --- /dev/null +++ b/src/go/plugins/vfs/dir/size_nix.go @@ -0,0 +1,73 @@ +// +build !windows + +/* +** Zabbix +** Copyright (C) 2001-2021 Zabbix SIA +** +** 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; either version 2 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +**/ + +package dir + +import ( + "fmt" + "io/fs" + "os" + "syscall" +) + +func (sp *sizeParams) getSize(fs fs.FileInfo, path string) (int64, error) { + sys, ok := fs.Sys().(*syscall.Stat_t) + if !ok { + return 0, fmt.Errorf("failed to read %s file size", fs.Name()) + } + + if !sp.diskMode { + return sys.Size, nil + } + + return sys.Blocks * diskBlockSize, nil +} + +func skipDir(d fs.DirEntry) bool { + return false +} + +func (sp *sizeParams) handleHomeDir(path string, d fs.DirEntry) (int64, error) { + parentSize, err := sp.getParentSize(d) + if err != nil { + return 0, err + } + + return parentSize, nil +} + +func (cp *common) osSkip(path string, d fs.DirEntry) bool { + i, err := d.Info() + if err != nil { + impl.Logger.Errf("failed to get file info for path %s, %s", path, err.Error()) + return true + } + + for _, f := range cp.files { + if os.SameFile(f, i) { + return true + } + } + + cp.files = append(cp.files, i) + + return false +} diff --git a/src/go/plugins/vfs/dir/size_windows.go b/src/go/plugins/vfs/dir/size_windows.go new file mode 100644 index 00000000000..84f12d6a0c3 --- /dev/null +++ b/src/go/plugins/vfs/dir/size_windows.go @@ -0,0 +1,108 @@ +/* +** Zabbix +** Copyright (C) 2001-2021 Zabbix SIA +** +** 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; either version 2 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +**/ + +package dir + +import ( + "fmt" + "io/fs" + "os" + "syscall" + + "zabbix.com/pkg/win32" +) + +func (sp *sizeParams) handleHomeDir(path string, d fs.DirEntry) (int64, error) { + if sp.diskMode { + _, len, err := win32.GetFullPathName(path) + if err != nil { + return 0, err + } + + sp.disk, err = win32.GetVolumePathName(path, len) + if err != nil { + return 0, err + } + } + + // home dir is not counter on windows + return 0, nil +} + +func skipDir(d fs.DirEntry) bool { + if d.IsDir() { + return true + } + + return false +} + +func (sp *sizeParams) getSize(fs fs.FileInfo, path string) (int64, error) { + sys, ok := fs.Sys().(*syscall.Win32FileAttributeData) + if !ok { + return 0, fmt.Errorf("failed to read %s file size", fs.Name()) + } + + fileSize := uint64(sys.FileSizeHigh)<<32 | uint64(sys.FileSizeLow) + + if sp.diskMode { + clusterSize, err := sp.getClusterSize(path) + if err != nil { + return 0, err + } + + mod := fileSize % clusterSize + if mod != 0 { + fileSize += clusterSize - mod + } + } + + return int64(fileSize), nil +} + +func (sp *sizeParams) getClusterSize(path string) (uint64, error) { + clusters, err := win32.GetDiskFreeSpace(sp.disk) + if err != nil { + return 0, err + } + + return uint64(clusters.LpSectorsPerCluster) * uint64(clusters.LpBytesPerSector), nil +} + +func (cp *common) osSkip(path string, d fs.DirEntry) bool { + if d.Type() == fs.ModeSymlink { + return true + } + + i, err := d.Info() + if err != nil { + impl.Logger.Errf("failed to get file info for path %s, %s", path, err.Error()) + return true + } + + for _, f := range cp.files { + if os.SameFile(f, i) { + return true + } + } + + cp.files = append(cp.files, i) + + return false +} diff --git a/src/go/plugins/zabbix/sync/sync_nix.go b/src/go/plugins/zabbix/sync/sync_nix.go index 607096b7118..17192f049c1 100644 --- a/src/go/plugins/zabbix/sync/sync_nix.go +++ b/src/go/plugins/zabbix/sync/sync_nix.go @@ -1,3 +1,4 @@ +//go:build !windows // +build !windows /* @@ -25,8 +26,6 @@ func getMetrics() []string { return []string{ "net.dns", "Checks if DNS service is up.", "net.dns.record", "Performs DNS query.", - "vfs.dir.count", "Directory entry count.", "vfs.dir.get", "Directory entry list.", - "vfs.dir.size", "Directory size (in bytes).", } } diff --git a/src/go/plugins/zabbix/sync/sync_windows.go b/src/go/plugins/zabbix/sync/sync_windows.go index f1848a797d7..62701cfb8df 100644 --- a/src/go/plugins/zabbix/sync/sync_windows.go +++ b/src/go/plugins/zabbix/sync/sync_windows.go @@ -23,8 +23,6 @@ func getMetrics() []string { return []string{ "net.dns", "Checks if DNS service is up.", "net.dns.record", "Performs DNS query.", - "vfs.dir.count", "Directory entry count.", "vfs.dir.get", "Directory entry list.", - "vfs.dir.size", "Directory size (in bytes).", } } |