Repository: rabbitstack/fibratus (language: python)

Issue:
File path filter functions
File path functions can be convenient when building detection rules where the full executable or file path is given in the filter field. Still, we're interested in just the file name or the directory within the file is located. The Go standard library ships with many useful utility [routines](https://pkg.go.dev/path/filepath) for manipulating filename paths. Most of the filter functions can directly map to the corresponding Go counterpart. The following is the proposal for file path filter functions:

- `base`: returns the last element of the path
- `dir`: returns all but the last element of the path, typically the path's directory
- `symlink`: returns the path name after the evaluation of any symbolic links
- `ext`: returns the file name extension used by the path
- `glob`:  returns the names of all files matching the pattern or an empty list if there is no matching file
- `is_abs`: reports whether the path is absolute
- `volume`: returns leading volume name


Discussion / hints:

Fix patch:
diff --git a/pkg/filter/ql/function.go b/pkg/filter/ql/function.go
index 0d9d21e06..9720aedeb 100644
--- a/pkg/filter/ql/function.go
+++ b/pkg/filter/ql/function.go
@@ -60,7 +60,14 @@ var funcs = map[string]FunctionDef{
 	functions.SubstrFn.String():       &functions.Substr{},
 	functions.EntropyFn.String():      &functions.Entropy{},
 	functions.RegexFn.String():        functions.NewRegex(),
-	functions.IsMinidumpFn.String():   functions.IsMinidump{},
+	functions.IsMinidumpFn.String():   &functions.IsMinidump{},
+	functions.BaseFn.String():         &functions.Base{},
+	functions.DirFn.String():          &functions.Dir{},
+	functions.SymlinkFn.String():      &functions.Symlink{},
+	functions.ExtFn.String():          &functions.Ext{},
+	functions.GlobFn.String():         &functions.Glob{},
+	functions.IsAbsFn.String():        &functions.IsAbs{},
+	functions.VolumeFn.String():       &functions.Volume{},
 }
 
 // FunctionDef is the interface that all function definitions have to satisfy.
diff --git a/pkg/filter/ql/functions/base.go b/pkg/filter/ql/functions/base.go
new file mode 100644
index 000000000..e05fcc01a
--- /dev/null
+++ b/pkg/filter/ql/functions/base.go
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2021-2022 by Nedim Sabic Sabic
+ * https://www.fibratus.io
+ * All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package functions
+
+import (
+	"path/filepath"
+	"strings"
+)
+
+// Base returns the last element of the path.
+type Base struct{}
+
+func (f Base) Call(args []interface{}) (interface{}, bool) {
+	if len(args) < 1 {
+		return false, false
+	}
+	switch s := args[0].(type) {
+	case string:
+		return f.trimExt(filepath.Base(s), args), true
+	case []string:
+		paths := make([]string, len(s))
+		for i, path := range s {
+			paths[i] = f.trimExt(filepath.Base(path), args)
+		}
+		return paths, true
+	}
+	return nil, true
+}
+
+func (f Base) Desc() FunctionDesc {
+	desc := FunctionDesc{
+		Name: BaseFn,
+		Args: []FunctionArgDesc{
+			{Keyword: "path", Types: []ArgType{Field, Func, String, Slice}, Required: true},
+			{Keyword: "ext", Types: []ArgType{Bool}, Required: false},
+		},
+	}
+	return desc
+}
+
+func (f Base) Name() Fn { return BaseFn }
+
+func (f Base) trimExt(base string, args []interface{}) string {
+	if len(args) > 1 {
+		ext, ok := args[1].(bool)
+		if !ok {
+			return base
+		}
+		if !ext {
+			n := strings.LastIndex(base, ".")
+			if n > 0 {
+				return base[:n]
+			}
+		}
+	}
+	return base
+}
diff --git a/pkg/filter/ql/functions/dir.go b/pkg/filter/ql/functions/dir.go
new file mode 100644
index 000000000..d5b95cde0
--- /dev/null
+++ b/pkg/filter/ql/functions/dir.go
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2021-2022 by Nedim Sabic Sabic
+ * https://www.fibratus.io
+ * All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package functions
+
+import "path/filepath"
+
+// Dir returns all but the last element of the path, typically the path's directory.
+type Dir struct{}
+
+func (f Dir) Call(args []interface{}) (interface{}, bool) {
+	if len(args) < 1 {
+		return false, false
+	}
+	switch s := args[0].(type) {
+	case string:
+		return filepath.Dir(s), true
+	case []string:
+		dirs := make([]string, len(s))
+		for i, path := range s {
+			dirs[i] = filepath.Dir(path)
+		}
+		return dirs, true
+	}
+	return nil, true
+}
+
+func (f Dir) Desc() FunctionDesc {
+	desc := FunctionDesc{
+		Name: DirFn,
+		Args: []FunctionArgDesc{
+			{Keyword: "path", Types: []ArgType{Field, Func, String, Slice}, Required: true},
+		},
+	}
+	return desc
+}
+
+func (f Dir) Name() Fn { return DirFn }
diff --git a/pkg/filter/ql/functions/ext.go b/pkg/filter/ql/functions/ext.go
new file mode 100644
index 000000000..e6476e821
--- /dev/null
+++ b/pkg/filter/ql/functions/ext.go
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2021-2022 by Nedim Sabic Sabic
+ * https://www.fibratus.io
+ * All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package functions
+
+import "path/filepath"
+
+// Ext returns the file name extension used by the path.
+type Ext struct{}
+
+func (f Ext) Call(args []interface{}) (interface{}, bool) {
+	if len(args) < 1 {
+		return false, false
+	}
+	path := parseString(0, args)
+	ext := filepath.Ext(path)
+	if len(args) > 1 {
+		dot, ok := args[1].(bool)
+		if !ok {
+			return ext, true
+		}
+		if !dot {
+			return ext[1:], true
+		}
+	}
+	return ext, true
+}
+
+func (f Ext) Desc() FunctionDesc {
+	desc := FunctionDesc{
+		Name: ExtFn,
+		Args: []FunctionArgDesc{
+			{Keyword: "path", Types: []ArgType{Field, Func, String}, Required: true},
+			{Keyword: "dot", Types: []ArgType{Bool}, Required: false},
+		},
+	}
+	return desc
+}
+
+func (f Ext) Name() Fn { return ExtFn }
diff --git a/pkg/filter/ql/functions/glob.go b/pkg/filter/ql/functions/glob.go
new file mode 100644
index 000000000..83d008432
--- /dev/null
+++ b/pkg/filter/ql/functions/glob.go
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2021-2022 by Nedim Sabic Sabic
+ * https://www.fibratus.io
+ * All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package functions
+
+import (
+	"path/filepath"
+)
+
+// Glob returns the names of all files matching the pattern or an empty list if there is no matching file.
+type Glob struct{}
+
+func (f Glob) Call(args []interface{}) (interface{}, bool) {
+	if len(args) < 1 {
+		return false, false
+	}
+	pattern, ok := args[0].(string)
+	if !ok {
+		return false, false
+	}
+	matches, err := filepath.Glob(pattern)
+	if err != nil {
+		return nil, true
+	}
+	return matches, true
+}
+
+func (f Glob) Desc() FunctionDesc {
+	desc := FunctionDesc{
+		Name: GlobFn,
+		Args: []FunctionArgDesc{
+			{Keyword: "pattern", Types: []ArgType{Field, Func, String}, Required: true},
+		},
+	}
+	return desc
+}
+
+func (f Glob) Name() Fn { return GlobFn }
diff --git a/pkg/filter/ql/functions/is_abs.go b/pkg/filter/ql/functions/is_abs.go
new file mode 100644
index 000000000..c7c995a69
--- /dev/null
+++ b/pkg/filter/ql/functions/is_abs.go
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2021-2022 by Nedim Sabic Sabic
+ * https://www.fibratus.io
+ * All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package functions
+
+import "path/filepath"
+
+// IsAbs reports whether the path is absolute.
+type IsAbs struct{}
+
+func (f IsAbs) Call(args []interface{}) (interface{}, bool) {
+	if len(args) < 1 {
+		return false, false
+	}
+	path := parseString(0, args)
+	return filepath.IsAbs(path), true
+}
+
+func (f IsAbs) Desc() FunctionDesc {
+	desc := FunctionDesc{
+		Name: IsAbsFn,
+		Args: []FunctionArgDesc{
+			{Keyword: "path", Types: []ArgType{Field, Func, String}, Required: true},
+		},
+	}
+	return desc
+}
+
+func (f IsAbs) Name() Fn { return IsAbsFn }
diff --git a/pkg/filter/ql/functions/symlink.go b/pkg/filter/ql/functions/symlink.go
new file mode 100644
index 000000000..e0fba6608
--- /dev/null
+++ b/pkg/filter/ql/functions/symlink.go
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2021-2022 by Nedim Sabic Sabic
+ * https://www.fibratus.io
+ * All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package functions
+
+import "path/filepath"
+
+// Symlink returns the path name after the evaluation of any symbolic links.
+type Symlink struct{}
+
+func (f Symlink) Call(args []interface{}) (interface{}, bool) {
+	if len(args) < 1 {
+		return false, false
+	}
+	path := parseString(0, args)
+	newpath, err := filepath.EvalSymlinks(path)
+	if err != nil {
+		return path, true
+	}
+	return newpath, true
+}
+
+func (f Symlink) Desc() FunctionDesc {
+	desc := FunctionDesc{
+		Name: SymlinkFn,
+		Args: []FunctionArgDesc{
+			{Keyword: "path", Types: []ArgType{Field, Func, String}, Required: true},
+		},
+	}
+	return desc
+}
+
+func (f Symlink) Name() Fn { return SymlinkFn }
diff --git a/pkg/filter/ql/functions/types.go b/pkg/filter/ql/functions/types.go
index e2b1f76d2..98eb99489 100644
--- a/pkg/filter/ql/functions/types.go
+++ b/pkg/filter/ql/functions/types.go
@@ -54,6 +54,20 @@ const (
 	RegexFn
 	// IsMinidumpFn represents the ISMINIDUMP function
 	IsMinidumpFn
+	// BaseFn represents the BASE function
+	BaseFn
+	// DirFn represents the DIR function
+	DirFn
+	// SymlinkFn represents the SYMLINK function
+	SymlinkFn
+	// ExtFn represents the EXT function
+	ExtFn
+	// GlobFn represents the GLOB function
+	GlobFn
+	// IsAbsFn represents the IS_ABS function
+	IsAbsFn
+	// VolumeFn represents the VOLUME function
+	VolumeFn
 )
 
 // ArgType is the type alias for the argument value type.
@@ -78,6 +92,8 @@ const (
 	Func
 	// Slice represents the string slice argument type.
 	Slice
+	// Bool represents the boolean argument type.
+	Bool
 	// Unknown is the unknown argument type.
 	Unknown
 )
@@ -97,6 +113,8 @@ func (typ ArgType) String() string {
 		return "func"
 	case Slice:
 		return "slice"
+	case Bool:
+		return "bool"
 	}
 	return "unknown"
 }
@@ -170,6 +188,18 @@ func (f Fn) String() string {
 		return "REGEX"
 	case IsMinidumpFn:
 		return "IS_MINIDUMP"
+	case BaseFn:
+		return "BASE"
+	case DirFn:
+		return "DIR"
+	case ExtFn:
+		return "EXT"
+	case GlobFn:
+		return "GLOB"
+	case IsAbsFn:
+		return "IS_ABS"
+	case VolumeFn:
+		return "VOLUME"
 	default:
 		return "UNDEFINED"
 	}
diff --git a/pkg/filter/ql/functions/volume.go b/pkg/filter/ql/functions/volume.go
new file mode 100644
index 000000000..a8efb6eee
--- /dev/null
+++ b/pkg/filter/ql/functions/volume.go
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2021-2022 by Nedim Sabic Sabic
+ * https://www.fibratus.io
+ * All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package functions
+
+import "path/filepath"
+
+// Volume returns leading volume name.
+type Volume struct{}
+
+func (f Volume) Call(args []interface{}) (interface{}, bool) {
+	if len(args) < 1 {
+		return false, false
+	}
+	path := parseString(0, args)
+	return filepath.VolumeName(path), true
+}
+
+func (f Volume) Desc() FunctionDesc {
+	desc := FunctionDesc{
+		Name: VolumeFn,
+		Args: []FunctionArgDesc{
+			{Keyword: "path", Types: []ArgType{Field, Func, String}, Required: true},
+		},
+	}
+	return desc
+}
+
+func (f Volume) Name() Fn { return VolumeFn }
diff --git a/pkg/filter/ql/literal.go b/pkg/filter/ql/literal.go
index c0594b3d3..5255bc4a1 100644
--- a/pkg/filter/ql/literal.go
+++ b/pkg/filter/ql/literal.go
@@ -179,6 +179,8 @@ func (f Function) validate() error {
 			typ = functions.Func
 		case reflect.TypeOf(&ListLiteral{}):
 			typ = functions.Slice
+		case reflect.TypeOf(&BoolLiteral{}):
+			typ = functions.Bool
 		}
 		if !arg.ContainsType(typ) {
 			return ErrArgumentTypeMismatch(i, arg.Keyword, fn.Name(), arg.Types)
diff --git a/pkg/filter/ql/parser.go b/pkg/filter/ql/parser.go
index 7b24d4831..b6ba89a8f 100644
--- a/pkg/filter/ql/parser.go
+++ b/pkg/filter/ql/parser.go
@@ -158,7 +158,7 @@ func (p *Parser) ParseExpr() (Expr, error) {
 			// expect LPAREN after in
 			tok, pos, lit := p.scanIgnoreWhitespace()
 			p.unscan()
-			if tok != Lparen && !p.c.IsMacroList(lit) {
+			if tok != Lparen && (p.c != nil && !p.c.IsMacroList(lit)) {
 				return nil, newParseError(tokstr(op, lit), []string{"'('"}, pos, p.expr)
 			}
 		}


Test patch:
diff --git a/pkg/filter/filter_test.go b/pkg/filter/filter_test.go
index a2dfbf627..027bc0dc3 100644
--- a/pkg/filter/filter_test.go
+++ b/pkg/filter/filter_test.go
@@ -312,6 +312,16 @@ func TestFilterRunFileKevent(t *testing.T) {
 		{`file.name ifuzzy 'C:\\WINDOWS\\sYS\\32dll'`, true},
 		{`file.name fuzzy ('C:\\Windows\\system32\\kernel', 'C:\\Windows\\system32\\ser3ll')`, true},
 		{`file.name ifuzzynorm 'C:\\WINDOWS\\sÝS\\32dll'`, true},
+		{`base(file.name) = 'user32.dll'`, true},
+		{`ext(base(file.name)) = '.dll'`, true},
+		{`base(file.name, false) = 'user32'`, true},
+		{`dir(file.name) = 'C:\\Windows\\system32'`, true},
+		{`ext(file.name) = '.dll'`, true},
+		{`ext(file.name, false) = 'dll'`, true},
+		{`is_abs(file.name)`, true},
+		{`is_abs(base(file.name))`, false},
+		{`file.name iin glob('C:\\Windows\\System32\\*.dll')`, true},
+		{`volume(file.name) = 'C:'`, true},
 	}
 
 	for i, tt := range tests {
diff --git a/pkg/filter/ql/function_test.go b/pkg/filter/ql/function_test.go
index 2d062dffa..dbab13106 100644
--- a/pkg/filter/ql/function_test.go
+++ b/pkg/filter/ql/function_test.go
@@ -42,6 +42,7 @@ func TestParseFunction(t *testing.T) {
 		{expr: "replace('hello world', 'hello', 'hell', 'world', 'war', 'hello')", err: errors.New("old/new replacements mismatch")},
 		{expr: "replace('hello world', 'hello', 'hell', 'world', 'war', 'hello', 'warld', 'old', 'new', 'one')", err: errors.New("old/new replacements mismatch")},
 		{expr: "indexof('hello', 'h', 'frst')", err: errors.New("frst is not a valid index search order")},
+		{expr: "base('C:\\\\Windows\\\\cmd.exe', false)"},
 	}
 
 	for i, tt := range tests {
@@ -49,7 +50,7 @@ func TestParseFunction(t *testing.T) {
 		_, err := p.ParseExpr()
 		if err == nil && tt.err != nil {
 			t.Errorf("%d. exp=%s expected error=%v", i, tt.expr, tt.err)
-		} else if err != nil {
+		} else if err != nil && tt.err != nil {
 			assert.True(t, strings.Contains(err.Error(), tt.err.Error()))
 		} else if err != nil && tt.err == nil {
 			t.Errorf("%d. exp=%s got error=%v", i, tt.expr, err)
diff --git a/pkg/filter/ql/functions/base_test.go b/pkg/filter/ql/functions/base_test.go
new file mode 100644
index 000000000..599136202
--- /dev/null
+++ b/pkg/filter/ql/functions/base_test.go
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2021-2022 by Nedim Sabic Sabic
+ * https://www.fibratus.io
+ * All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package functions
+
+import (
+	"fmt"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+func TestBase(t *testing.T) {
+	var tests = []struct {
+		args     []interface{}
+		expected interface{}
+	}{
+		{
+			[]interface{}{"C:\\Windows\\cmd.exe"},
+			"cmd.exe",
+		},
+		{
+			[]interface{}{"C:\\Windows\\cmd.exe", false},
+			"cmd",
+		},
+		{
+			[]interface{}{[]string{"C:\\Windows\\cmd.exe", "C:\\Windows\\notepad.exe"}},
+			[]string{"cmd.exe", "notepad.exe"},
+		},
+		{
+			[]interface{}{[]string{"C:\\Windows\\cmd.exe", "C:\\Windows\\notepad.exe"}, false},
+			[]string{"cmd", "notepad"},
+		},
+	}
+
+	for i, tt := range tests {
+		f := Base{}
+		res, _ := f.Call(tt.args)
+		assert.Equal(t, tt.expected, res, fmt.Sprintf("%d. result mismatch: exp=%v got=%v", i, tt.expected, res))
+	}
+}
diff --git a/pkg/filter/ql/functions/cidr_test.go b/pkg/filter/ql/functions/cidr_test.go
index 4bc476f72..31b98a810 100644
--- a/pkg/filter/ql/functions/cidr_test.go
+++ b/pkg/filter/ql/functions/cidr_test.go
@@ -19,35 +19,52 @@
 package functions
 
 import (
+	"fmt"
 	"github.com/stretchr/testify/assert"
-	"github.com/stretchr/testify/require"
 	"net"
 	"testing"
 )
 
 func TestCIDRContainsCall(t *testing.T) {
-	call := CIDRContains{}
-
-	res, _ := call.Call([]interface{}{net.ParseIP("192.168.1.5"), "192.168.1.0/24"})
-	require.True(t, res.(bool))
-
-	res, _ = call.Call([]interface{}{net.ParseIP("216.58.201.174"), "216.58.201.1/24"})
-	require.True(t, res.(bool))
-
-	res, _ = call.Call([]interface{}{"192.168.1.5", "192.168.1.0/24"})
-	require.True(t, res.(bool))
-
-	res, _ = call.Call([]interface{}{net.ParseIP("192.168.1.5"), "172.168.1.0/24"})
-	require.False(t, res.(bool))
-
-	res, _ = call.Call([]interface{}{net.ParseIP("192.168.1.5"), "172.168.1.0/24", "192.168.1.0/24"})
-	require.True(t, res.(bool))
-
-	res, _ = call.Call([]interface{}{net.ParseIP("192.168.1.5"), "192.168.1.0"})
-	require.False(t, res.(bool))
+	var tests = []struct {
+		args     []interface{}
+		expected interface{}
+	}{
+		{
+			[]interface{}{net.ParseIP("192.168.1.5"), "192.168.1.0/24"},
+			true,
+		},
+		{
+			[]interface{}{net.ParseIP("216.58.201.174"), "216.58.201.1/24"},
+			true,
+		},
+		{
+			[]interface{}{"192.168.1.5", "192.168.1.0/24"},
+			true,
+		},
+		{
+			[]interface{}{net.ParseIP("192.168.1.5"), "172.168.1.0/24"},
+			false,
+		},
+		{
+			[]interface{}{net.ParseIP("192.168.1.5"), "172.168.1.0/24", "192.168.1.0/24"},
+			true,
+		},
+		{
+			[]interface{}{net.ParseIP("192.168.1.5"), "192.168.1.0"},
+			false,
+		},
+		{
+			[]interface{}{net.ParseIP("192.168.1.5")},
+			false,
+		},
+	}
 
-	res, _ = call.Call([]interface{}{net.ParseIP("192.168.1.5")})
-	require.False(t, res.(bool))
+	for i, tt := range tests {
+		f := CIDRContains{}
+		res, _ := f.Call(tt.args)
+		assert.Equal(t, tt.expected, res, fmt.Sprintf("%d. result mismatch: exp=%v got=%v", i, tt.expected, res))
+	}
 }
 
 func TestCIDRContainsDesc(t *testing.T) {
diff --git a/pkg/filter/ql/functions/dir_test.go b/pkg/filter/ql/functions/dir_test.go
new file mode 100644
index 000000000..e0c1ebba1
--- /dev/null
+++ b/pkg/filter/ql/functions/dir_test.go
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2021-2022 by Nedim Sabic Sabic
+ * https://www.fibratus.io
+ * All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package functions
+
+import (
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+func TestDir(t *testing.T) {
+	call := Dir{}
+	res, _ := call.Call([]interface{}{"C:\\Windows\\cmd.exe"})
+	assert.Equal(t, "C:\\Windows", res)
+}
diff --git a/pkg/filter/ql/functions/ext_test.go b/pkg/filter/ql/functions/ext_test.go
new file mode 100644
index 000000000..3d028ffd4
--- /dev/null
+++ b/pkg/filter/ql/functions/ext_test.go
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2021-2022 by Nedim Sabic Sabic
+ * https://www.fibratus.io
+ * All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package functions
+
+import (
+	"fmt"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+func TestExt(t *testing.T) {
+	var tests = []struct {
+		args     []interface{}
+		expected interface{}
+	}{
+		{
+			[]interface{}{"C:\\Windows\\cmd.exe"},
+			".exe",
+		},
+		{
+			[]interface{}{"C:\\Windows\\cmd.exe", false},
+			"exe",
+		},
+	}
+
+	for i, tt := range tests {
+		f := Ext{}
+		res, _ := f.Call(tt.args)
+		assert.Equal(t, tt.expected, res, fmt.Sprintf("%d. result mismatch: exp=%v got=%v", i, tt.expected, res))
+	}
+}
diff --git a/pkg/filter/ql/functions/glob_test.go b/pkg/filter/ql/functions/glob_test.go
new file mode 100644
index 000000000..8e9fb9a0b
--- /dev/null
+++ b/pkg/filter/ql/functions/glob_test.go
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2021-2022 by Nedim Sabic Sabic
+ * https://www.fibratus.io
+ * All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package functions
+
+import (
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+func TestGlob(t *testing.T) {
+	call := Glob{}
+	res, _ := call.Call([]interface{}{"C:\\Windows\\*.exe"})
+	assert.IsType(t, []string{}, res)
+	assert.Contains(t, res, "C:\\Windows\\notepad.exe")
+}
diff --git a/pkg/filter/ql/functions/indexof_test.go b/pkg/filter/ql/functions/indexof_test.go
index 8a2e307bb..62e129fa1 100644
--- a/pkg/filter/ql/functions/indexof_test.go
+++ b/pkg/filter/ql/functions/indexof_test.go
@@ -19,19 +19,34 @@
 package functions
 
 import (
+	"fmt"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
 )
 
 func TestIndexOf(t *testing.T) {
-	call := IndexOf{}
-	res, _ := call.Call([]interface{}{"hello world", "world"})
-	assert.Equal(t, 6, res)
+	var tests = []struct {
+		args     []interface{}
+		expected interface{}
+	}{
+		{
+			[]interface{}{"hello world", "world"},
+			6,
+		},
+		{
+			[]interface{}{"hello world", "brave"},
+			-1,
+		},
+		{
+			[]interface{}{"hello world brave world", "world", "last"},
+			18,
+		},
+	}
 
-	res1, _ := call.Call([]interface{}{"hello world", "brave"})
-	assert.Equal(t, -1, res1)
-
-	res2, _ := call.Call([]interface{}{"hello world brave world", "world", "last"})
-	assert.Equal(t, 18, res2)
+	for i, tt := range tests {
+		f := IndexOf{}
+		res, _ := f.Call(tt.args)
+		assert.Equal(t, tt.expected, res, fmt.Sprintf("%d. result mismatch: exp=%v got=%v", i, tt.expected, res))
+	}
 }
diff --git a/pkg/filter/ql/functions/is_abs_test.go b/pkg/filter/ql/functions/is_abs_test.go
new file mode 100644
index 000000000..c0052c0ed
--- /dev/null
+++ b/pkg/filter/ql/functions/is_abs_test.go
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2021-2022 by Nedim Sabic Sabic
+ * https://www.fibratus.io
+ * All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package functions
+
+import (
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+func TestIsAbs(t *testing.T) {
+	call := IsAbs{}
+	res, _ := call.Call([]interface{}{"C:\\Windows\\cmd.exe"})
+	assert.True(t, res.(bool))
+	res1, _ := call.Call([]interface{}{"Windows\\cmd.exe"})
+	assert.False(t, res1.(bool))
+}
diff --git a/pkg/filter/ql/functions/length_test.go b/pkg/filter/ql/functions/length_test.go
index 78b61f66a..602743655 100644
--- a/pkg/filter/ql/functions/length_test.go
+++ b/pkg/filter/ql/functions/length_test.go
@@ -19,19 +19,34 @@
 package functions
 
 import (
+	"fmt"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
 )
 
 func TestLength(t *testing.T) {
-	call := Length{}
-	res, _ := call.Call([]interface{}{"hello"})
-	assert.Equal(t, 5, res)
+	var tests = []struct {
+		args     []interface{}
+		expected interface{}
+	}{
+		{
+			[]interface{}{"hello"},
+			5,
+		},
+		{
+			[]interface{}{"こんにちは"},
+			5,
+		},
+		{
+			[]interface{}{[]string{"hello", "world"}},
+			2,
+		},
+	}
 
-	res1, _ := call.Call([]interface{}{"こんにちは"})
-	assert.Equal(t, 5, res1)
-
-	res2, _ := call.Call([]interface{}{[]string{"hello", "world"}})
-	assert.Equal(t, 2, res2)
+	for i, tt := range tests {
+		f := Length{}
+		res, _ := f.Call(tt.args)
+		assert.Equal(t, tt.expected, res, fmt.Sprintf("%d. result mismatch: exp=%v got=%v", i, tt.expected, res))
+	}
 }
diff --git a/pkg/filter/ql/functions/regex_test.go b/pkg/filter/ql/functions/regex_test.go
index 5aa72c187..179d88096 100644
--- a/pkg/filter/ql/functions/regex_test.go
+++ b/pkg/filter/ql/functions/regex_test.go
@@ -19,23 +19,38 @@
 package functions
 
 import (
+	"fmt"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
 )
 
 func TestRegex(t *testing.T) {
-	call := NewRegex()
-
-	res, _ := call.Call([]interface{}{`powershell.exe`, `power.*(shell|hell).exe`})
-	assert.True(t, res.(bool))
-
-	res1, _ := call.Call([]interface{}{`powershell.exe`, `power.*(shell|hell).dll`, `.*hell.exe`})
-	assert.True(t, res1.(bool))
+	var tests = []struct {
+		args     []interface{}
+		expected interface{}
+	}{
+		{
+			[]interface{}{`powershell.exe`, `power.*(shell|hell).exe`},
+			true,
+		},
+		{
+			[]interface{}{`powershell.exe`, `power.*(shell|hell).dll`, `.*hell.exe`},
+			true,
+		},
+		{
+			[]interface{}{`powershell.exe`, "[`"},
+			false,
+		},
+	}
 
-	res3, _ := call.Call([]interface{}{`powershell.exe`, "[`"})
-	assert.False(t, res3.(bool))
+	for i, tt := range tests {
+		f := NewRegex()
+		res, _ := f.Call(tt.args)
+		assert.Equal(t, tt.expected, res, fmt.Sprintf("%d. result mismatch: exp=%v got=%v", i, tt.expected, res))
+	}
 
+	call := NewRegex()
 	for i := 0; i < 10; i++ {
 		res, _ := call.Call([]interface{}{`powershell.exe`, `power.*(shell|hell).dll`, `.*hell.exe`})
 		assert.True(t, res.(bool))
diff --git a/pkg/filter/ql/functions/replace_test.go b/pkg/filter/ql/functions/replace_test.go
index 39cbd3fa9..24526054f 100644
--- a/pkg/filter/ql/functions/replace_test.go
+++ b/pkg/filter/ql/functions/replace_test.go
@@ -19,33 +19,42 @@
 package functions
 
 import (
+	"fmt"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
 )
 
 func TestReplace(t *testing.T) {
-	call := Replace{}
-	_, ok := call.Call([]interface{}{"hello world", "hello "})
-	assert.False(t, ok)
-
-	res, ok := call.Call([]interface{}{"hello world", "hello", "hell"})
-	assert.Equal(t, "hell world", res)
-	assert.True(t, ok)
-
-	res1, ok := call.Call([]interface{}{"hello world", "hello", "hell", "NO", "REPL"})
-	assert.Equal(t, "hell world", res1)
-	assert.True(t, ok)
-
-	res2, ok := call.Call([]interface{}{"hello world", "hello", "hell", "hell", "heaven", "world", "brave"})
-	assert.Equal(t, "heaven brave", res2)
-	assert.True(t, ok)
-
-	key, ok := call.Call([]interface{}{"HKEY_LOCAL_MACHINE\\SAM", "HKEY_LOCAL_MACHINE", "HKLM", "HKEY_CURRENT_USER\\Console", "HKCU"})
-	assert.Equal(t, "HKLM\\SAM", key)
-	assert.True(t, ok)
-
-	key1, ok := call.Call([]interface{}{"HKEY_CURRENT_USER\\Console", "HKEY_LOCAL_MACHINE", "HKLM", "HKEY_CURRENT_USER", "HKCU"})
-	assert.Equal(t, "HKCU\\Console", key1)
-	assert.True(t, ok)
+	var tests = []struct {
+		args     []interface{}
+		expected interface{}
+	}{
+		{
+			[]interface{}{"hello world", "hello", "hell"},
+			"hell world",
+		},
+		{
+			[]interface{}{"hello world", "hello", "hell", "NO", "REPL"},
+			"hell world",
+		},
+		{
+			[]interface{}{"hello world", "hello", "hell", "hell", "heaven", "world", "brave"},
+			"heaven brave",
+		},
+		{
+			[]interface{}{"HKEY_LOCAL_MACHINE\\SAM", "HKEY_LOCAL_MACHINE", "HKLM", "HKEY_CURRENT_USER\\Console", "HKCU"},
+			"HKLM\\SAM",
+		},
+		{
+			[]interface{}{"HKEY_CURRENT_USER\\Console", "HKEY_LOCAL_MACHINE", "HKLM", "HKEY_CURRENT_USER", "HKCU"},
+			"HKCU\\Console",
+		},
+	}
+
+	for i, tt := range tests {
+		f := Replace{}
+		res, _ := f.Call(tt.args)
+		assert.Equal(t, tt.expected, res, fmt.Sprintf("%d. result mismatch: exp=%v got=%v", i, tt.expected, res))
+	}
 }
diff --git a/pkg/filter/ql/functions/substr_test.go b/pkg/filter/ql/functions/substr_test.go
index ca479fdee..7344ee5f6 100644
--- a/pkg/filter/ql/functions/substr_test.go
+++ b/pkg/filter/ql/functions/substr_test.go
@@ -19,25 +19,42 @@
 package functions
 
 import (
+	"fmt"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
 )
 
 func TestSubstr(t *testing.T) {
-	call := Substr{}
-	res, _ := call.Call([]interface{}{"Hello", 0, 4})
-	assert.Equal(t, "Hell", res)
-
-	res1, _ := call.Call([]interface{}{"Hello World!", 0, 50})
-	assert.Equal(t, "Hello World!", res1)
-
-	res2, _ := call.Call([]interface{}{"Hello World!", 4, -1})
-	assert.Equal(t, "Hello World!", res2)
-
-	res3, _ := call.Call([]interface{}{"Hello World!", -1, 10})
-	assert.Equal(t, "Hello World!", res3)
-
-	res4, _ := call.Call([]interface{}{"Hello World!", 6, 7})
-	assert.Equal(t, "W", res4)
+	var tests = []struct {
+		args     []interface{}
+		expected interface{}
+	}{
+		{
+			[]interface{}{"Hello", 0, 4},
+			"Hell",
+		},
+		{
+			[]interface{}{"Hello World!", 0, 50},
+			"Hello World!",
+		},
+		{
+			[]interface{}{"Hello World!", 4, -1},
+			"Hello World!",
+		},
+		{
+			[]interface{}{"Hello World!", -1, 10},
+			"Hello World!",
+		},
+		{
+			[]interface{}{"Hello World!", 6, 7},
+			"W",
+		},
+	}
+
+	for i, tt := range tests {
+		f := Substr{}
+		res, _ := f.Call(tt.args)
+		assert.Equal(t, tt.expected, res, fmt.Sprintf("%d. result mismatch: exp=%v got=%v", i, tt.expected, res))
+	}
 }
diff --git a/pkg/filter/ql/functions/symlink_test.go b/pkg/filter/ql/functions/symlink_test.go
new file mode 100644
index 000000000..ab8dc8601
--- /dev/null
+++ b/pkg/filter/ql/functions/symlink_test.go
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2021-2022 by Nedim Sabic Sabic
+ * https://www.fibratus.io
+ * All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package functions
+
+import (
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+	"os"
+	"path/filepath"
+	"testing"
+)
+
+var testDir = filepath.Join(os.TempDir(), "test")
+
+func TestSymlink(t *testing.T) {
+	ln, err := createSymlink()
+	require.NoError(t, err)
+	call := Symlink{}
+	res, _ := call.Call([]interface{}{ln})
+	defer func() {
+		_ = os.RemoveAll(testDir)
+	}()
+	assert.Equal(t, "target.txt", filepath.Base(res.(string)))
+}
+
+func createSymlink() (string, error) {
+	target := "target.txt"
+	if err := os.MkdirAll(testDir, 0755); err != nil {
+		return "", err
+	}
+	if err := os.WriteFile(filepath.Join(testDir, "target.txt"), []byte("Test\n"), os.ModePerm); err != nil {
+		return "", err
+	}
+	symlink := filepath.Join(testDir, "symlink.txt")
+	return symlink, os.Symlink(target, symlink)
+}
diff --git a/pkg/filter/ql/functions/volume_test.go b/pkg/filter/ql/functions/volume_test.go
new file mode 100644
index 000000000..f42e54b96
--- /dev/null
+++ b/pkg/filter/ql/functions/volume_test.go
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2021-2022 by Nedim Sabic Sabic
+ * https://www.fibratus.io
+ * All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package functions
+
+import (
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+func TestVolume(t *testing.T) {
+	call := Volume{}
+	res, _ := call.Call([]interface{}{"C:\\Windows\\cmd.exe"})
+	assert.Equal(t, "C:", res)
+	res1, _ := call.Call([]interface{}{`\\host\share\docs`})
+	assert.Equal(t, "\\\\host\\share", res1)
+}