--- /dev/null
- test_expression_no_evaluate('xattr_match("value", "name")')
- test_expression_no_evaluate('xattr_match("value*", "name")')
- test_expression_no_evaluate('xattr_match("value*", "name*")')
- test_expression_no_evaluate('xattr_match("*value*", "*name*")')
- test_expression_no_evaluate('xattr_match("value" , "*name*")')
- test_expression_no_evaluate('xattr_match("value" , "name")')
- test_expression_no_evaluate('xattr_match("value","name")')
- test_expression_no_evaluate('xattr_match( "value","name")')
- test_expression_no_evaluate('xattr_match( "value","name")')
- test_expression_no_evaluate('xattr_match("value","name" )')
- test_expression_no_evaluate('xattr_match("value","name" )')
- # xattr_match("\"value", "name")
- test_expression_no_evaluate('xattr_match("\\\"value", "name")')
- # xattr_match("value\"", "name")
- test_expression_no_evaluate('xattr_match("value\\\"", "name")')
- # xattr_match("value", "\"name")
- test_expression_no_evaluate('xattr_match("value", "\\\"name")')
- # xattr_match("value", "\"name\"")
- test_expression_no_evaluate('xattr_match("value", "name\\\"")')
- # xattr_match("\\value", "name")
- test_expression_no_evaluate('xattr_match("\\\\value", "name")')
- # xattr_match("value", "\\name")
- test_expression_no_evaluate('xattr_match("value", "\\\\name")')
- # xattr_match("\\value\\", "\\name\\")
- test_expression_no_evaluate('xattr_match("\\\\value\\\\", "\\\\name\\\\")')
- test_expression_invalid('xattr_match(value, name)')
- test_expression_invalid('xattr_match(value, "name")')
- test_expression_invalid('xattr_match("value", name)')
- test_expression_invalid('xattr_match("value", "name)')
- test_expression_invalid('xattr_match("value", name")')
- test_expression_invalid('xattr_match("value" "name")')
- test_expression_invalid('xattr_match"value", "name")')
- test_expression_invalid('xattr_match("value", "name"')
- test_expression_invalid('xattr_match("value")')
- test_expression_invalid('xattr_match(value)')
- test_expression_invalid('xattr_match(\\"value", "name")')
- test_expression_invalid('xattr_match("value\\", "name")')
- test_expression_invalid('xattr_match("value", \\"name")')
- test_expression_invalid('xattr_match("value", "name\\")')
+#!/usr/bin/python2 -u
+# pylint: disable=too-many-lines
+# Copyright (c) 2019 DataDirect Networks, Inc.
+# All Rights Reserved.
+# Author: lixi@ddn.com
+"""
+Script to check whether expression evaluation works well
+"""
+import os
+import sys
+
+# Local libs
+from pylustre import clog
+from pylustre import utils
+from pylipe import lipe_constant
+from pylipe import lipe_find
+
+LOG = None
+EXE_FPATH = None
+
+DONT_EVALUATE = "--dont-evaluate"
+
+# Please refer to lipe_expression_test.c for the usage of the RETVAL_*
+# Success
+RETVAL_SUCCESS = 0
+# Help message without any other operation
+RETVAL_HELP = 1
+# The argument to this program is invalid
+RETVAL_ARG_INVALID = 2
+# The attribute name is invalid
+RETVAL_ATTR_NAME_INVALID = 3
+# The attribute value is invalid
+RETVAL_ATTR_VALUE_INVALID = 4
+# The expression is invalid
+RETVAL_EXPRESSION_INVALID = 5
+# The expression value is not expected
+RETVAL_UNEXPECTED_VALUE = 6
+# Unexpected internal error
+RETVAL_INTERNAL_ERROR = 7
+# Attribute has no value
+RETVAL_ATTR_NO_VALUE = 8
+
+
+def test_expression_invalid(expression):
+ """
+ Run lipe_expression_test on the expression
+ """
+ test_expression(expression, expect_retval=RETVAL_EXPRESSION_INVALID)
+
+
+def test_expression_no_evaluate(expression):
+ """
+ Run lipe_expression_test on the expression
+ """
+ test_expression(expression, dont_evaluate=True)
+
+
+def test_expression(expression, expected_value=None, options="",
+ dont_evaluate=False, expect_retval=RETVAL_SUCCESS):
+ """
+ Run lipe_expression_test on the expression
+ """
+ if dont_evaluate:
+ no_evaluation_string = " " + DONT_EVALUATE
+ else:
+ no_evaluation_string = ""
+
+ command = ("%s%s %s '%s'" %
+ (EXE_FPATH, no_evaluation_string, options, expression))
+
+ if not dont_evaluate:
+ command += " '%s'" % expected_value
+
+ retval = utils.run(command)
+ if retval.cr_exit_status != expect_retval:
+ LOG.cl_error("unexpected ret [%s] (expected [%s]) of command "
+ "[%s], stdout = [%s], stderr = [%s], ",
+ retval.cr_exit_status, expect_retval,
+ command, retval.cr_stdout, retval.cr_stderr)
+ raise Exception("test failure")
+ else:
+ if options != "":
+ expression_string = "[%s] on [%s]" % (expression, options)
+ else:
+ expression_string = "[%s]" % expression
+ if expect_retval == RETVAL_SUCCESS:
+ if dont_evaluate:
+ LOG.cl_info("%s -> valid ... checked", expression_string)
+ else:
+ LOG.cl_info("%s == %s ... checked", expression_string, expected_value)
+ else:
+ LOG.cl_info("%s --> failure(%s) ... checked", expression_string,
+ expect_retval)
+
+
+def test_size_expressions(attribute="size", has_unit=True, bits=64):
+ """
+ test expressions of size/blocks
+ """
+ # pylint: disable=too-many-statements
+ sizes = [0, 1, 1023, 1024, 65534]
+ if bits == 32:
+ # 1048576 = 1M, 1024 * 1048576 = 1G = 2^30
+ sizes += [65535, 65536, 1048576, 1024 * 1048576, 1024 * 1048576 * 4 - 2]
+ if bits == 64:
+ # 1048576 * 1048576 = 1T, 1048576 * 1048576 * 1024 = 1P,
+ # 1048576 * 1048576 * 1048576 = 1E = 2^60
+ sizes += [1024 * 1048576 * 4 - 1, 1024 * 1048576 * 4,
+ 1024 * 1048576 * 4 + 1, 1048576 * 1048576,
+ 1048576 * 1048576 * 1024, 1048576 * 1048576 * 1048576]
+ for size in sizes:
+ find_size = (lipe_find.LipeFindSize.LFS_EXPRESSION_INFIX % (attribute, size))
+ find_size_prefix = (lipe_find.LipeFindSize.LFS_EXPRESSION_PREFIX % (attribute, size))
+ find_size_greater = (lipe_find.LipeFindSize.LFS_GREATER_EXPRESSION_INFIX %
+ (attribute, size))
+ find_size_greater_prefix = (lipe_find.LipeFindSize.LFS_GREATER_EXPRESSION_PREFIX %
+ (attribute, size))
+ find_size_less = (lipe_find.LipeFindSize.LFS_LESS_EXPRESSION_INFIX %
+ (attribute, size))
+ find_size_less_prefix = (lipe_find.LipeFindSize.LFS_LESS_EXPRESSION_PREFIX %
+ (attribute, size))
+
+ test_expression(find_size, 1, options="--%s %s" % (attribute, size))
+ test_expression(find_size_prefix, 1, options="--%s %s" % (attribute, size))
+ test_expression(find_size_greater, 0, options="--%s %s" % (attribute, size))
+ test_expression(find_size_greater_prefix, 0, options="--%s %s" % (attribute, size))
+ test_expression(find_size_less, 0, options="--%s %s" % (attribute, size))
+ test_expression(find_size_less_prefix, 0, options="--%s %s" % (attribute, size))
+
+ test_expression(find_size, 0,
+ options="--%s %s" % (attribute, (size + 1)))
+ test_expression(find_size_prefix, 0,
+ options="--%s %s" % (attribute, (size + 1)))
+ test_expression(find_size_greater, 1,
+ options="--%s %s" % (attribute, (size + 1)))
+ test_expression(find_size_greater_prefix, 1,
+ options="--%s %s" % (attribute, (size + 1)))
+ test_expression(find_size_less, 0,
+ options="--%s %s" % (attribute, (size + 1)))
+ test_expression(find_size_less_prefix, 0,
+ options="--%s %s" % (attribute, (size + 1)))
+
+ if size < 1:
+ continue
+ test_expression(find_size, 0,
+ options="--%s %s" % (attribute, (size - 1)))
+ test_expression(find_size_prefix, 0,
+ options="--%s %s" % (attribute, (size - 1)))
+ test_expression(find_size_greater, 0,
+ options="--%s %s" % (attribute, (size - 1)))
+ test_expression(find_size_greater_prefix, 0,
+ options="--%s %s" % (attribute, (size - 1)))
+ test_expression(find_size_less, 1,
+ options="--%s %s" % (attribute, (size - 1)))
+ test_expression(find_size_less_prefix, 1,
+ options="--%s %s" % (attribute, (size - 1)))
+
+ if not has_unit:
+ return
+
+ unit_dict = {}
+ unit_dict["512"] = 512
+ unit_dict["K"] = 1024
+ unit_dict["M"] = 1048576
+ unit_dict["G"] = 1048576 * 1024
+ unit_dict["T"] = 1048576 * 1048576
+ unit_dict["P"] = 1048576 * 1048576 * 1024
+ unit_dict["E"] = 1048576 * 1048576 * 1048576
+ for unit, size in unit_dict.iteritems():
+ find_size = (lipe_find.LipeFindSize.LFS_UNIT_EXPRESSION_INFIX %
+ (attribute, 1, unit, attribute, 1, unit))
+ find_size_prefix = (lipe_find.LipeFindSize.LFS_UNIT_EXPRESSION_PREFIX %
+ (attribute, 1, unit, attribute, 1, unit))
+ find_size_greater = (lipe_find.LipeFindSize.LFS_GREATER_UNIT_EXPRESSION_INFIX %
+ (attribute, 1, unit))
+ find_size_greater_prefix = (lipe_find.LipeFindSize.LFS_GREATER_UNIT_EXPRESSION_PREFIX %
+ (attribute, 1, unit))
+ find_size_less = (lipe_find.LipeFindSize.LFS_LESS_UNIT_EXPRESSION_INFIX %
+ (attribute, 1, unit))
+ find_size_less_prefix = (lipe_find.LipeFindSize.LFS_LESS_UNIT_EXPRESSION_PREFIX %
+ (attribute, 1, unit))
+
+ test_expression(find_size, 0, options="--%s 0" % attribute)
+ test_expression(find_size_prefix, 0, options="--%s 0" % attribute)
+ test_expression(find_size_greater, 0, options="--%s 0" % attribute)
+ test_expression(find_size_greater_prefix, 0, options="--%s 0" % attribute)
+ test_expression(find_size_less, 1, options="--%s 0" % attribute)
+ test_expression(find_size_less_prefix, 1, options="--%s 0" % attribute)
+
+ test_expression(find_size, 0,
+ options="--%s %s" % (attribute, (size - 1)))
+ test_expression(find_size_prefix, 0,
+ options="--%s %s" % (attribute, (size - 1)))
+ test_expression(find_size_greater, 0,
+ options="--%s %s" % (attribute, (size - 1)))
+ test_expression(find_size_greater_prefix, 0,
+ options="--%s %s" % (attribute, (size - 1)))
+ test_expression(find_size_less, 1,
+ options="--%s %s" % (attribute, (size - 1)))
+ test_expression(find_size_less_prefix, 1,
+ options="--%s %s" % (attribute, (size - 1)))
+
+ test_expression(find_size, 1, options="--%s %s" % (attribute, size))
+ test_expression(find_size_prefix, 1, options="--%s %s" % (attribute, size))
+ test_expression(find_size_greater, 0, options="--%s %s" % (attribute, size))
+ test_expression(find_size_greater_prefix, 0, options="--%s %s" % (attribute, size))
+ test_expression(find_size_less, 0, options="--%s %s" % (attribute, size))
+ test_expression(find_size_less_prefix, 0, options="--%s %s" % (attribute, size))
+
+ test_expression(find_size, 1,
+ options="--%s %s" % (attribute, (size + 1)))
+ test_expression(find_size_prefix, 1,
+ options="--%s %s" % (attribute, (size + 1)))
+ test_expression(find_size_greater, 0,
+ options="--%s %s" % (attribute, (size + 1)))
+ test_expression(find_size_greater_prefix, 0,
+ options="--%s %s" % (attribute, (size + 1)))
+ test_expression(find_size_less, 0,
+ options="--%s %s" % (attribute, (size + 1)))
+ test_expression(find_size_less_prefix, 0,
+ options="--%s %s" % (attribute, (size + 1)))
+
+ test_expression(find_size, 1,
+ options="--%s %s" % (attribute, (size * 2 - 1)))
+ test_expression(find_size_prefix, 1,
+ options="--%s %s" % (attribute, (size * 2 - 1)))
+ test_expression(find_size_greater, 0,
+ options="--%s %s" % (attribute, (size * 2 - 1)))
+ test_expression(find_size_greater_prefix, 0,
+ options="--%s %s" % (attribute, (size * 2 - 1)))
+ test_expression(find_size_less, 0,
+ options="--%s %s" % (attribute, (size * 2 - 1)))
+ test_expression(find_size_less_prefix, 0,
+ options="--%s %s" % (attribute, (size * 2 - 1)))
+
+ test_expression(find_size, 0,
+ options="--%s %s" % (attribute, (size * 2)))
+ test_expression(find_size_prefix, 0,
+ options="--%s %s" % (attribute, (size * 2)))
+ test_expression(find_size_greater, 1,
+ options="--%s %s" % (attribute, (size * 2)))
+ test_expression(find_size_greater_prefix, 1,
+ options="--%s %s" % (attribute, (size * 2)))
+ test_expression(find_size_less, 0,
+ options="--%s %s" % (attribute, (size * 2)))
+ test_expression(find_size_less_prefix, 0,
+ options="--%s %s" % (attribute, (size * 2)))
+
+ test_expression(find_size, 0,
+ options="--%s %s" % (attribute, (size * 2 + 1)))
+ test_expression(find_size_prefix, 0,
+ options="--%s %s" % (attribute, (size * 2 + 1)))
+ test_expression(find_size_greater, 1,
+ options="--%s %s" % (attribute, (size * 2 + 1)))
+ test_expression(find_size_greater_prefix, 1,
+ options="--%s %s" % (attribute, (size * 2 + 1)))
+ test_expression(find_size_less, 0,
+ options="--%s %s" % (attribute, (size * 2 + 1)))
+ test_expression(find_size_less_prefix, 0,
+ options="--%s %s" % (attribute, (size * 2 + 1)))
+
+ test_expression(find_size, 0,
+ options="--%s %s" % (attribute, (size * 3)))
+ test_expression(find_size_prefix, 0,
+ options="--%s %s" % (attribute, (size * 3)))
+ test_expression(find_size_greater, 1,
+ options="--%s %s" % (attribute, (size * 3)))
+ test_expression(find_size_greater_prefix, 1,
+ options="--%s %s" % (attribute, (size * 3)))
+ test_expression(find_size_less, 0,
+ options="--%s %s" % (attribute, (size * 3)))
+ test_expression(find_size_less_prefix, 0,
+ options="--%s %s" % (attribute, (size * 3)))
+
+
+def main_with_exception():
+ """
+ Run tests of expression evaluation, do no catch exception
+ """
+ # pylint: disable=global-statement,too-many-statements,too-many-locals
+ global LOG, EXE_FPATH
+ LOG = clog.get_log(simple_console=True)
+ my_exe_fpath = os.path.abspath(sys.argv[0])
+ dir_path = os.path.dirname(my_exe_fpath)
+ EXE_FPATH = dir_path + "/src/lipe_expression_test"
+
+ # Option tests for lipe_expression_test
+ test_expression("1", options="-h", expect_retval=RETVAL_HELP)
+ test_expression("1", options="--help", expect_retval=RETVAL_HELP)
+ test_expression("1", options="--help", expect_retval=RETVAL_HELP)
+
+ # Number tests
+ test_expression("4294967296", 4294967296)
+ test_expression("4294967297", 4294967297)
+
+ # 2^64 = 18446744073709551616
+ # 2^63 = 9223372036854775808
+ # 2^32 = 4294967296
+ # 2^31 = 2147483648
+ test_expression("9223372036854775807", 9223372036854775807)
+ test_expression_invalid("9223372036854775808")
+ test_expression("-9223372036854775808", -9223372036854775808)
+ test_expression("-9223372036854775808 == -9223372036854775808", 1)
+ test_expression("== -9223372036854775808 -9223372036854775808", 1)
+ test_expression("9223372036854775807 + 1", -9223372036854775808)
+ test_expression("+ 9223372036854775807 1", -9223372036854775808)
+ test_expression("9223372036854775807 + 2", -9223372036854775807)
+ test_expression("+ 9223372036854775807 2", -9223372036854775807)
+ test_expression("9223372036854775807 + 3", -9223372036854775806)
+ test_expression("+ 9223372036854775807 3", -9223372036854775806)
+ test_expression("2147483648 * 2147483648 * 2", -9223372036854775808)
+ test_expression("* * 2147483648 2147483648 2", -9223372036854775808)
+ test_expression("4294967296 * 4294967296", 0)
+ test_expression("* 4294967296 4294967296", 0)
+ # Overflowed value won't be accepted
+ test_expression_invalid("18446744073709551615")
+ test_expression_invalid("18446744073709551616")
+ test_expression_invalid("18446744073709551617")
+ # Not allow to seperate - with the number
+ test_expression_invalid("- 2")
+ test_expression("-2", -2)
+ test_expression_invalid("1 - - 2")
+ test_expression_invalid("- 1 - 2")
+ test_expression("1 - -2", 3)
+ test_expression("- 1 -2", 3)
+ test_expression_invalid("1 * - 2")
+ test_expression_invalid("* 1 - 2")
+ test_expression("1 * -2", -2)
+ test_expression("* 1 -2", -2)
+
+ # Base 16 support
+ #
+ # 2^64 = 0x10000000000000000 = 18446744073709551616
+ # 2^63 = 0x8000000000000000 = 9223372036854775808
+ # 2^32 = 0x100000000 = 4294967296
+ # 2^31 = 0x08 = 2147483648
+ test_expression("0x00", 0)
+ test_expression("-0x0", 0)
+ test_expression("-0x1", -1)
+ test_expression("-0x1 + 0", -1)
+ test_expression("+ -0x1 0", -1)
+ test_expression("-0x02 + -0", -2)
+ test_expression("+ -0x02 -0", -2)
+ test_expression("-0X02 + -0", -2)
+ test_expression("+ -0X02 -0", -2)
+ test_expression("0x7FFFFFFFFFFFFFFF", 9223372036854775807)
+ test_expression_invalid("0x8000000000000000")
+ test_expression("-0x7fffffffffffffff", -9223372036854775807)
+ test_expression("-0x7fffffffffffffff == -9223372036854775807", 1)
+ test_expression("== -0x7fffffffffffffff -9223372036854775807", 1)
+ test_expression("-0x8000000000000000", -9223372036854775808)
+ test_expression("-0x8000000000000000 == -9223372036854775808", 1)
+ test_expression("== -0x8000000000000000 -9223372036854775808", 1)
+ test_expression_invalid("-0x8000000000000001")
+ test_expression("-0x01 + 0x02", 1)
+ test_expression("+ -0x01 0x02", 1)
+ test_expression("-0x02 + 0x1", -1)
+ test_expression("+ -0x02 0x1", -1)
+ test_expression("0x1 - 0x2", -1)
+ test_expression("- 0x1 0x2", -1)
+ test_expression("0x1 - -0x2", 3)
+ test_expression("- 0x1 -0x2", 3)
+ test_expression("0xff + 0x1 == 0x100", 1)
+ test_expression("== + 0xff 0x1 0x100", 1)
+ test_expression("0xaf + 0x2 == 0xb1", 1)
+ test_expression("== + 0xaf 0x2 0xb1", 1)
+
+ # Base 8 support
+ #
+ # 2^64 = 02000000000000000000000 = 18446744073709551616
+ # 0123456789012345678901
+ # 2^63 = 01000000000000000000000 = 9223372036854775808
+ # 0123456789012345678901
+ # 2^63 - 1 = 0777777777777777777777 = 9223372036854775807
+ # 0123456789012345678901
+ # 2^32 = 040000000000 = 4294967296
+ # 01234567890
+ test_expression("00", 0)
+ test_expression("-00", 0)
+ test_expression("-01", -1)
+ test_expression("-01 + 0", -1)
+ test_expression("+ -01 0", -1)
+ test_expression("-02 + -0", -2)
+ test_expression("+ -02 -0", -2)
+ test_expression("-02 + -0", -2)
+ test_expression("+ -02 -0", -2)
+ test_expression("-07 + -0", -7)
+ test_expression("+ -07 -0", -7)
+ test_expression_invalid("08")
+ test_expression_invalid("09")
+ test_expression_invalid("0A")
+ test_expression_invalid("0a")
+ test_expression("0777777777777777777777", 9223372036854775807)
+ test_expression_invalid("01000000000000000000000")
+ test_expression("-0777777777777777777777", -9223372036854775807)
+ test_expression("-0777777777777777777777 == -9223372036854775807", 1)
+ test_expression(" == -0777777777777777777777 -9223372036854775807", 1)
+ test_expression("-01000000000000000000000", -9223372036854775808)
+ test_expression("-01000000000000000000000 == -9223372036854775808", 1)
+ test_expression("== -01000000000000000000000 -9223372036854775808", 1)
+ test_expression("-01 + 02", 1)
+ test_expression("+ -01 02", 1)
+ test_expression("-02 + 01", -1)
+ test_expression("+ -02 01", -1)
+ test_expression("01 - 02", -1)
+ test_expression("- 01 02", -1)
+ test_expression("01 - -02", 3)
+ test_expression("- 01 -02", 3)
+ test_expression("077 + 01 == 0100", 1)
+ test_expression("== + 077 01 0100", 1)
+ test_expression("067 + 012 == 0101", 1)
+ test_expression("== + 067 012 0101", 1)
+ test_expression_no_evaluate('(mode & S_IRWXG) == 060')
+ test_expression_no_evaluate('== & mode S_IRWXG 060')
+ test_expression_no_evaluate('(mode & (S_IRWXU | S_IRWXG | S_IRWXO)) == 0770')
+ test_expression_no_evaluate('== & mode | | S_IRWXU S_IRWXG S_IRWXO 0770')
+
+ # !!! Do not crash when divided by 0
+ test_expression("1 / 0", 9223372036854775807)
+ test_expression("/ 1 0", 9223372036854775807)
+ test_expression("99 / 0", 9223372036854775807)
+ test_expression("/ 99 0", 9223372036854775807)
+ test_expression("9223372036854775807 / 0", 9223372036854775807)
+ test_expression("/ 9223372036854775807 0", 9223372036854775807)
+ test_expression("1 % 0", 1)
+ test_expression("% 1 0", 1)
+ test_expression("99 % 0", 99)
+ test_expression("% 99 0", 99)
+ test_expression("9223372036854775807 % 0", 9223372036854775807)
+ test_expression("% 9223372036854775807 0", 9223372036854775807)
+
+ # Bool
+ test_expression("100 == 100", 1)
+ test_expression("== 100 100", 1)
+ test_expression("99 == 100", 0)
+ test_expression("== 99 100", 0)
+
+ test_expression("1 + 2", 3)
+ test_expression("1 + 2", 3)
+ test_expression("+ 1 2", 3)
+
+ # Byte constants
+ test_expression("KB", 1000)
+ test_expression("MB", 1000 * 1000)
+ test_expression("GB", 1000 * 1000 * 1000)
+ test_expression("TB", 1000 * 1000 * 1000 * 1000)
+ test_expression("PB", 1000 * 1000 * 1000 * 1000 * 1000)
+ test_expression("KB * 1000 == MB", 1)
+ test_expression("MB * 1000 == GB", 1)
+ test_expression("GB * 1000 == TB", 1)
+ test_expression("TB * 1000 == PB", 1)
+ test_expression("K", 1024)
+ test_expression("M", 1024 * 1024)
+ test_expression("G", 1024 * 1024 * 1024)
+ test_expression("T", 1024 * 1024 * 1024 * 1024)
+ test_expression("P", 1024 * 1024 * 1024 * 1024 * 1024)
+ test_expression("K * 1024 == M", 1)
+ test_expression("== * K 1024 M", 1)
+ test_expression("M * 1024 == G", 1)
+ test_expression("== * M 1024 G", 1)
+ test_expression("G * 1024 == T", 1)
+ test_expression("== * G 1024 T", 1)
+ test_expression("T * 1024 == P", 1)
+ test_expression("== * T 1024 P", 1)
+ test_expression("KB + 24 == K", 1)
+ test_expression("== + KB 24 K", 1)
+
+ # Time constants
+ test_expression("second", 1000)
+ test_expression("minute", 60 * 1000)
+ test_expression("hour", 3600 * 1000)
+ test_expression("day", 24 * 3600 * 1000)
+ test_expression("week", 7 * 24 * 3600 * 1000)
+ test_expression("month", 30 * 24 * 3600 * 1000)
+ test_expression("year", 365 * 24 * 3600 * 1000)
+ test_expression("seconds", 1000)
+ test_expression("minutes", 60 * 1000)
+ test_expression("hours", 3600 * 1000)
+ test_expression("days", 24 * 3600 * 1000)
+ test_expression("weeks", 7 * 24 * 3600 * 1000)
+ test_expression("months", 30 * 24 * 3600 * 1000)
+ test_expression("years", 365 * 24 * 3600 * 1000)
+ test_expression("s", 1000)
+ test_expression("m", 60 * 1000)
+ test_expression("h", 3600 * 1000)
+ test_expression("d", 24 * 3600 * 1000)
+ test_expression("w", 7 * 24 * 3600 * 1000)
+ test_expression("y", 365 * 24 * 3600 * 1000)
+ test_expression("10 * seconds", 10 * 1000)
+ test_expression("15 * minutes", 15 * 60 * 1000)
+ test_expression("8 * hours", 8 * 3600 * 1000)
+ test_expression("12 * days", 12 * 24 * 3600 * 1000)
+ test_expression("7 * weeks", 7 * 7 * 24 * 3600 * 1000)
+ test_expression("4 * months", 4 * 30 * 24 * 3600 * 1000)
+ test_expression("2 * years", 2 * 365 * 24 * 3600 * 1000)
+ test_expression("90 * s == m / 2 * 3", 1)
+ test_expression("60 * seconds == minute", 1)
+ test_expression("90 * minutes == hour * 3 / 2", 1)
+ test_expression("72 * hours == 3 * days", 1)
+ test_expression("7 * days == week", 1)
+ test_expression("4 * weeks + 2 * days == month", 1)
+ test_expression("2 * months == 60 * days", 1)
+ test_expression("12 * months + 5 * day == year", 1)
+ test_expression("52 * weeks + 1 * day == year", 1)
+
+ # Functions
+ test_expression_invalid('fname_reg')
+ test_expression_invalid('fname_reg(')
+ test_expression_invalid('fname_reg(x')
+ test_expression_invalid('fname_reg(x)')
+ test_expression_invalid('fname_reg(x) ')
+ test_expression_invalid('fname_reg("')
+ test_expression_invalid('fname_reg("x')
+ test_expression_invalid('fname_reg("x"')
+ test_expression_invalid('fname_reg("x)')
+ test_expression_invalid('fname_reg(x")')
+ test_expression_invalid('fname_reg("x" ')
+ # Escaped \" should not be considered as the end. \\ will be transfered
+ # to \, so the actual expression is fname_reg("x\"), which is invalid.
+ test_expression_invalid('fname_reg("x\\")')
+ # fname_reg(\"x") is invalid
+ test_expression_invalid('fname_reg(\\"x")')
+ # fname_reg("\") is invalid
+ test_expression_invalid('fname_match("\\")')
+ # fname_reg("\\\") is invalid
+ test_expression_invalid('fname_match("\\\\\\")')
+ # fname_reg("\\\\\") is invalid
+ test_expression_invalid('fname_match("\\\\\\\\\\")')
+ test_expression_invalid("fname_match('x')")
+
+ test_expression_no_evaluate('fname_reg("x")')
+ test_expression_no_evaluate('fname_reg("x") ')
+ test_expression_no_evaluate('fname_reg("x") ')
+ test_expression_no_evaluate(' fname_reg("x")')
+ test_expression_no_evaluate(' fname_reg("x")')
+ test_expression_no_evaluate(' fname_reg("x") ')
+ test_expression_no_evaluate(' fname_reg("x") ')
+ test_expression_no_evaluate('fname_reg("x") + 1')
+ test_expression_no_evaluate('+ fname_reg("x") 1')
+ test_expression_no_evaluate('(fname_reg("x"))')
+ test_expression_no_evaluate('(fname_reg("x") )')
+ test_expression_no_evaluate('(fname_reg("x") )')
+ test_expression_no_evaluate('( fname_reg("x"))')
+ test_expression_no_evaluate('( fname_reg("x"))')
+ test_expression_no_evaluate('( fname_reg("x") )')
+ test_expression_no_evaluate('( fname_reg("x") )')
+ test_expression_no_evaluate('fname_reg( "x")')
+ test_expression_no_evaluate('fname_reg( "x")')
+ test_expression_no_evaluate('fname_reg("x" )')
+ test_expression_no_evaluate('fname_reg("x" )')
+ test_expression_no_evaluate('fname_reg( "x" )')
+ test_expression_no_evaluate('fname_reg( "x" )')
+ test_expression_no_evaluate('fname_reg( "x" )')
+ test_expression_no_evaluate('fname_reg("x ")')
+ test_expression_no_evaluate('fname_reg("x ")')
+ test_expression_no_evaluate('fname_reg(" x")')
+ test_expression_no_evaluate('fname_reg(" x ")')
+ test_expression_no_evaluate('fname_reg(" x")')
+ test_expression_no_evaluate('fname_reg(" x ")')
+ test_expression_no_evaluate('fname_reg(" x ")')
+ test_expression_no_evaluate('fname_reg(".+")')
+ # testing escape of \". \\ will be changed to \, thus actual expression is
+ # fname_reg("\"").
+ test_expression_no_evaluate('fname_reg("\\"")')
+ test_expression_no_evaluate('fname_reg("\\"\\"")')
+ test_expression_no_evaluate('fname_reg("\\"\\"\\"\\"")')
+ test_expression_no_evaluate('fname_reg("\\"\\"\\"\\"\\"")')
+ # testing escape of \\, \\ will be changed to \, thus actual expression is
+ # fname_match("\\"). And this actually matches file that has name \. Note that
+ # \ is not a valid expression, so fname_reg() would fail.
+ test_expression_no_evaluate('fname_match("\\\\")')
+ # fname_match("\\\\") is valid
+ test_expression_no_evaluate('fname_match("\\\\\\\\")')
+ # fname_match("\\\\\\") is valid
+ test_expression_no_evaluate('fname_match("\\\\\\\\\\\\")')
+ # fname_match("\\x\\") is valid
+ test_expression_no_evaluate('fname_match("\\\\x\\\\")')
+ # fname_match("\\x\x\\") is valid
+ test_expression_no_evaluate('fname_match("\\\\x\\x\\\\")')
+ # fname_match("\\x\x\x\x\x\\") is valid
+ test_expression_no_evaluate('fname_match("\\\\x\\x\\x\\x\\x\\\\")')
+ test_expression_no_evaluate('fname_match("fname")')
+ test_expression_no_evaluate('fname_match("fname*")')
+ test_expression_no_evaluate('fname_match("fname?0")')
+ test_expression_no_evaluate('fname_match("fname[0-9]")')
+ test_expression_no_evaluate('fname_match("fname[A-Z]")')
+ test_expression_no_evaluate('fname_match("fname[^0-8]")')
+ test_expression_no_evaluate('fname_match("fname[!a-d]")')
+ test_expression_no_evaluate('fname_match("fname[]4]")')
+
+ # ost() tests
+ test_expression_invalid('ost')
+ test_expression_invalid('ost(')
+ test_expression_invalid('ost(0')
+ test_expression_invalid('ost()')
+ test_expression_invalid('ost)')
+ test_expression_invalid('ost("')
+ test_expression_invalid('ost(""')
+ test_expression_invalid('ost("")')
+ test_expression_invalid('ost("0")')
+ test_expression_invalid('ost(-1)')
+ test_expression_invalid('ost(-9223372036854775807)')
+ test_expression_invalid('ost(-9223372036854775808)')
+ test_expression_invalid('ost(9223372036854775808)')
+ test_expression_no_evaluate('ost(9223372036854775807)')
+ test_expression_no_evaluate('ost(0)')
+ test_expression_no_evaluate('ost( 0) ')
+ test_expression_no_evaluate('ost(0 ) ')
+ test_expression_no_evaluate('ost( 0 ) ')
+ test_expression_no_evaluate('ost( 0 ) ')
+ test_expression_no_evaluate('ost(0) + 1')
+ test_expression_no_evaluate('+ ost(0) 1')
+ test_expression_no_evaluate('ost(0xFF)')
+ test_expression_no_evaluate('ost(0xEA)')
+ test_expression_no_evaluate('ost(0x000a)')
+ test_expression_no_evaluate('ost(07)')
+ test_expression_no_evaluate('ost(0777)')
+ test_expression_invalid("ost(08)")
+ test_expression_invalid("ost(0a)")
+
+ # pool_match() tests
+ test_expression_no_evaluate('pool_match("pool")')
+ test_expression_no_evaluate('pool_match("pool*")')
+ test_expression_no_evaluate('pool_match("pool?0")')
+ test_expression_no_evaluate('pool_match("pool[0-9]")')
+ test_expression_no_evaluate('pool_match("pool9[A-Z]")')
+ test_expression_no_evaluate('pool_match("pool9[^0-8]")')
+ test_expression_no_evaluate('pool_match("pool9[!1-3]")')
+ test_expression_no_evaluate('pool_match("pool0[]4]")')
+
+ # pool_reg() tests
+ test_expression_no_evaluate('pool_match("pool")')
+ test_expression_no_evaluate('pool_match("pool.*")')
+ test_expression_no_evaluate('pool_match("pool.+")')
+
+ # user() tests
+ test_expression_no_evaluate('user("root")')
+ test_expression_no_evaluate('user("0")')
+ # user() doesn't support base 16
+ test_expression_invalid('user("0x0")')
+ test_expression_invalid('user("0xA")')
+ test_expression_invalid('user("this_user_doesnot_exist")')
+
+ # group() tests
+ test_expression_no_evaluate('group("root")')
+ test_expression_no_evaluate('group("0")')
+ test_expression_invalid('group(0)')
+ # group() doesn't support base 16 number
+ test_expression_invalid('group(root)')
+ test_expression_invalid('group("0x0")')
+ test_expression_invalid('group("0xA")')
+ test_expression_invalid('group("this_group_doesnot_exist")')
+
+ # user2id() tests
+ test_expression('user2id("root")', 0)
+ test_expression('user2id("root") == uid', 1, options="--uid 0")
+ test_expression('user2id("root") == uid', 0, options="--uid 100")
+
+ # group2id() tests
+ test_expression('group2id("root")', 0)
+ test_expression('group2id("root") == gid', 1, options="--gid 0")
+ test_expression('group2id("root") == gid', 0, options="--gid 100")
+
+ # xattr_match() tests
++ # xattr_match() tests
++ test_expression_no_evaluate('xattr_match("name", "value")')
++ test_expression_no_evaluate('xattr_match("name*", "value")')
++ test_expression_no_evaluate('xattr_match("name*", "value*")')
++ test_expression_no_evaluate('xattr_match("*name*", "*value*")')
++ test_expression_no_evaluate('xattr_match("name" , "*value*")')
++ test_expression_no_evaluate('xattr_match("name" , "value")')
++ test_expression_no_evaluate('xattr_match("name","value")')
++ test_expression_no_evaluate('xattr_match( "name","value")')
++ test_expression_no_evaluate('xattr_match( "name","value")')
++ test_expression_no_evaluate('xattr_match("name","value" )')
++ test_expression_no_evaluate('xattr_match("name","value" )')
++ # xattr_match("\"name", "value")
++ test_expression_no_evaluate('xattr_match("\\\"name", "value")')
++ # xattr_match("name\"", "value")
++ test_expression_no_evaluate('xattr_match("name\\\"", "value")')
++ # xattr_match("name", "\"value")
++ test_expression_no_evaluate('xattr_match("name", "\\\"value")')
++ # xattr_match("name", "\"value\"")
++ test_expression_no_evaluate('xattr_match("name", "value\\\"")')
++ # xattr_match("\\name", "value")
++ test_expression_no_evaluate('xattr_match("\\\\name", "value")')
++ # xattr_match("name", "\\value")
++ test_expression_no_evaluate('xattr_match("name", "\\\\value")')
++ # xattr_match("\\name\\", "\\value\\")
++ test_expression_no_evaluate('xattr_match("\\\\name\\\\", "\\\\value\\\\")')
++ test_expression_invalid('xattr_match(name, value)')
++ test_expression_invalid('xattr_match(name, "value")')
++ test_expression_invalid('xattr_match("name", value)')
++ test_expression_invalid('xattr_match("name", "value)')
++ test_expression_invalid('xattr_match("name", value")')
++ test_expression_invalid('xattr_match("name" "value")')
++ test_expression_invalid('xattr_match"name", "value")')
++ test_expression_invalid('xattr_match("name", "value"')
++ test_expression_invalid('xattr_match("name")')
++ test_expression_invalid('xattr_match(name)')
++ test_expression_invalid('xattr_match(\\"name", "value")')
++ test_expression_invalid('xattr_match("name\\", "value")')
++ test_expression_invalid('xattr_match("name", \\"value")')
++ test_expression_invalid('xattr_match("name", "value\\")')
++
++ # xattr_name() tests
++ test_expression_no_evaluate('xattr_name("name")')
++ test_expression_no_evaluate('xattr_name("name*")')
++ test_expression_no_evaluate('xattr_name("name*")')
++ test_expression_no_evaluate('xattr_name("*name*")')
++ test_expression_no_evaluate('xattr_name("name" )')
++ test_expression_no_evaluate('xattr_name("name" )')
++ test_expression_no_evaluate('xattr_name( "name")')
++ test_expression_no_evaluate('xattr_name( "name")')
++ test_expression_no_evaluate('xattr_name("name")')
++ test_expression_no_evaluate('xattr_name("name")')
++ # xattr_name("\"name")')
++ test_expression_no_evaluate('xattr_name("\\\"name")')
++ # xattr_name("name\"")')
++ test_expression_no_evaluate('xattr_name("name\\\"")')
++ # xattr_name("name")')
++ test_expression_no_evaluate('xattr_name("name")')
++ # xattr_name("name")')
++ test_expression_no_evaluate('xattr_name("name")')
++ # xattr_name("\\name")')
++ test_expression_no_evaluate('xattr_name("\\\\name")')
++ # xattr_name("name")')
++ test_expression_no_evaluate('xattr_name("name")')
++ # xattr_name("\\name\\")')
++ test_expression_no_evaluate('xattr_name("\\\\name\\\\")')
++ test_expression_invalid('xattr_name(name)')
++ test_expression_invalid('xattr_name(name)')
++ test_expression_invalid('xattr_name("name", "value")')
++ test_expression_invalid('xattr_name("name" "value")')
++ test_expression_invalid('xattr_name("name",)')
++ test_expression_invalid('xattr_name"name")')
++ test_expression_invalid('xattr_name(name)')
++ test_expression_invalid('xattr_name(\\"name")')
++ test_expression_invalid('xattr_name("name\\")')
+
+ test_expression_invalid('perm_equal()')
+ test_expression_invalid('perm_equal(8)')
+ test_expression_invalid('perm_equal(0778)')
+ test_expression_invalid('perm_equal(0877)')
+ test_expression_invalid('perm_equal(010000)')
+ test_expression_invalid('perm_equal("07777")')
+
+ test_expression('perm_equal(07777)', 1, options="--mode 07777")
+ test_expression('perm_equal(07777)', 0, options="--mode 07776")
+ test_expression('perm_equal(04567)', 1, options="--mode 04567")
+ test_expression('perm_equal(03567)', 0, options="--mode 04567")
+ test_expression('perm_equal(04467)', 0, options="--mode 04567")
+ test_expression('perm_equal(04577)', 0, options="--mode 04567")
+
+ test_expression_invalid('perm_at_least()')
+ test_expression_invalid('perm_at_least(8)')
+ test_expression_invalid('perm_at_least(0778)')
+ test_expression_invalid('perm_at_least(0877)')
+ test_expression_invalid('perm_at_least(010000)')
+ test_expression_invalid('perm_at_least("07777")')
+ test_expression('perm_at_least(07777)', 1, options="--mode 07777")
+ test_expression('perm_at_least(07777)', 0, options="--mode 07776")
+ test_expression('perm_at_least(07776)', 1, options="--mode 07777")
+ test_expression('perm_at_least(04242)', 1, options="--mode 06666")
+ test_expression('perm_at_least(02424)', 1, options="--mode 06666")
+ test_expression('perm_at_least(03424)', 0, options="--mode 06666")
+
+ test_expression_invalid('perm_any()')
+ test_expression_invalid('perm_any(8)')
+ test_expression_invalid('perm_any(0778)')
+ test_expression_invalid('perm_any(0877)')
+ test_expression_invalid('perm_any(010000)')
+ test_expression_invalid('perm_any("07777")')
+ test_expression('perm_any(07777)', 1, options="--mode 07777")
+ test_expression('perm_any(07777)', 1, options="--mode 01000")
+ test_expression('perm_any(01010)', 1, options="--mode 07006")
+ test_expression('perm_any(01010)', 1, options="--mode 01000")
+ test_expression('perm_any(01010)', 1, options="--mode 00010")
+ test_expression('perm_any(00000)', 1, options="--mode 07777")
+ test_expression('perm_any(00000)', 1, options="--mode 00000")
+ test_expression('perm_any(01000)', 0, options="--mode 00777")
+ test_expression('perm_any(00001)', 0, options="--mode 07770")
+
+ test_expression_no_evaluate('layout("released")')
+ test_expression_no_evaluate('layout("raid0")')
+ test_expression_no_evaluate('layout("mdt")')
+ test_expression_no_evaluate('layout("mdt,released")')
+ test_expression_no_evaluate('layout("mdt,raid0,released")')
+ test_expression_invalid('layout(",")')
+ test_expression_invalid('layout("released,")')
+ test_expression_invalid('layout("raid0x")')
+ test_expression_invalid('layout(",mdt")')
+ test_expression_invalid('layout("released, raid0")')
+ test_expression_invalid('layout("raid0xraid0")')
+ test_expression_invalid('layout("mdt raid0")')
+ # Operator precedences:
+ #
+ # 9: * / %
+ # 8: + -
+ # 7: >> <<
+ # 6: < > <= >=
+ # 5: == !=
+ # 4: &
+ # 3: ^
+ # 2: |
+ # 1: &&
+ # 0: ||
+
+ test_expression("1 + 2 * 3", 7)
+ test_expression("+ 1 * 2 3", 7)
+
+ test_expression("1 + -2 * 3", -5)
+ test_expression("+ 1 * -2 3", -5)
+ test_expression("1 + -2 * -3", 7)
+ test_expression("+ 1 * -2 -3", 7)
+ test_expression("-1 + -2 * -3", 5)
+ test_expression("+ -1 * -2 -3", 5)
+
+ test_expression("1 + 6 / 2", 4)
+ test_expression("+ 1 / 6 2", 4)
+
+ test_expression("1 + 6 % 2", 1)
+ test_expression("+ 1 % 6 2", 1)
+
+ # + - from left to right
+ test_expression("3 + 2 - 1", 4)
+ test_expression("- + 3 2 1", 4)
+
+ # + - have greater precedences than >> <<
+ test_expression("1 + 1 << 1", 4)
+ test_expression("<< + 1 1 1", 4)
+ test_expression("2 - 1 << 1", 2)
+ test_expression("<< - 2 1 1", 2)
+ test_expression("4 + 4 >> 1", 4)
+ test_expression(">> + 4 4 1", 4)
+ test_expression("8 - 4 >> 1", 2)
+ test_expression(">> - 8 4 1", 2)
+
+ # >> << from left to right
+ # 3 >> 1: 1
+ test_expression("4 << 3 >> 1", 16)
+ test_expression(">> << 4 3 1", 16)
+ test_expression("4 << (3 >> 1)", 8)
+ test_expression("<< 4 >> 3 1", 8)
+ test_expression("4 >> 1 << 3", 16)
+ test_expression("<< >> 4 1 3", 16)
+ test_expression("4 >> (1 << 3)", 0)
+ test_expression(">> 4 << 1 3", 0)
+
+ # >> << have greater precedences than < > <= >=
+ test_expression("8 >> 1 < 4 + 1", 1)
+ test_expression("< >> 8 1 + 4 1", 1)
+ test_expression("8 >> 1 < 4 - 1", 0)
+ test_expression("< >> 8 1 - 4 1", 0)
+ test_expression("8 >> 1 > 4 - 1", 1)
+ test_expression("> >> 8 1 - 4 1", 1)
+ test_expression("8 >> 1 > 4 + 0", 0)
+ test_expression("> >> 8 1 + 4 0", 0)
+ test_expression("8 >> 1 > 4 + 1", 0)
+ test_expression("> >> 8 1 + 4 1", 0)
+ test_expression("8 >> 1 <= 4 + 1", 1)
+ test_expression("<= >> 8 1 + 4 1", 1)
+ test_expression("8 >> 1 <= 4 + 0", 1)
+ test_expression("<= >> 8 1 + 4 0", 1)
+ test_expression("8 >> 1 <= 4 - 1", 0)
+ test_expression("<= >> 8 1 - 4 1", 0)
+ test_expression("8 >> 1 >= 4 - 1", 1)
+ test_expression(">= >> 8 1 - 4 1", 1)
+ test_expression("8 >> 1 >= 4 + 0", 1)
+ test_expression(">= >> 8 1 + 4 0", 1)
+ test_expression("8 >> 1 >= 4 + 1", 0)
+ test_expression(">= >> 8 1 + 4 1", 0)
+
+ # >> << have greater precedences than < > <= >=
+ test_expression("8 << 1 < 16 + 1", 1)
+ test_expression("< << 8 1 + 16 1", 1)
+ test_expression("8 << 1 < 16 - 1", 0)
+ test_expression("< << 8 1 - 16 1", 0)
+ test_expression("8 << 1 > 16 - 1", 1)
+ test_expression("> << 8 1 - 16 1", 1)
+ test_expression("8 << 1 > 16 + 0", 0)
+ test_expression("> << 8 1 + 16 0", 0)
+ test_expression("8 << 1 > 16 + 1", 0)
+ test_expression("> << 8 1 + 16 1", 0)
+ test_expression("8 << 1 <= 16 + 1", 1)
+ test_expression("<= << 8 1 + 16 1", 1)
+ test_expression("8 << 1 <= 16 + 0", 1)
+ test_expression("<= << 8 1 + 16 0", 1)
+ test_expression("8 << 1 <= 16 - 1", 0)
+ test_expression("<= << 8 1 - 16 1", 0)
+ test_expression("8 << 1 >= 16 - 1", 1)
+ test_expression(">= << 8 1 - 16 1", 1)
+ test_expression("8 << 1 >= 16 + 0", 1)
+ test_expression(">= << 8 1 + 16 0", 1)
+ test_expression("8 << 1 >= 16 + 1", 0)
+ test_expression(">= << 8 1 + 16 1", 0)
+
+ # < > <= >= have greater precedences than == !=
+ # Test < and ==
+ test_expression("1 == 3 < 3 + 1", 1)
+ test_expression("== 1 < 3 + 3 1", 1)
+ test_expression("0 == 3 < 3 + 1", 0)
+ test_expression("== 0 < 3 + 3 1", 0)
+ test_expression("1 == 3 < 3 - 1", 0)
+ test_expression("== 1 < 3 - 3 1", 0)
+ test_expression("0 == 3 < 3 - 1", 1)
+ test_expression("== 0 < 3 - 3 1", 1)
+ test_expression("1 == 3 < 3 + 0", 0)
+ test_expression("== 1 < 3 + 3 0", 0)
+ test_expression("0 == 3 < 3 + 0", 1)
+ test_expression("== 0 < 3 + 3 0", 1)
+
+ # < > <= >= have greater precedences than == !=
+ # Test > and ==
+ test_expression("1 == 3 > 3 + 1", 0)
+ test_expression("== 1 > 3 + 3 1", 0)
+ test_expression("0 == 3 > 3 + 1", 1)
+ test_expression("== 0 > 3 + 3 1", 1)
+ test_expression("1 == 3 > 3 - 1", 1)
+ test_expression("== 1 > 3 - 3 1", 1)
+ test_expression("0 == 3 > 3 - 1", 0)
+ test_expression("== 0 > 3 - 3 1", 0)
+ test_expression("1 == 3 > 3 + 0", 0)
+ test_expression("== 1 > 3 + 3 0", 0)
+ test_expression("0 == 3 > 3 + 0", 1)
+ test_expression("== 0 > 3 + 3 0", 1)
+
+ # < > <= >= have greater precedences than == !=
+ # Test <= and ==
+ test_expression("1 == 3 <= 3 + 1", 1)
+ test_expression("== 1 <= 3 + 3 1", 1)
+ test_expression("0 == 3 <= 3 + 1", 0)
+ test_expression("== 0 <= 3 + 3 1", 0)
+ test_expression("1 == 3 <= 3 - 1", 0)
+ test_expression("== 1 <= 3 - 3 1", 0)
+ test_expression("0 == 3 <= 3 - 1", 1)
+ test_expression("== 0 <= 3 - 3 1", 1)
+ test_expression("1 == 3 <= 3 + 0", 1)
+ test_expression("== 1 <= 3 + 3 0", 1)
+ test_expression("0 == 3 <= 3 + 0", 0)
+ test_expression("== 0 <= 3 + 3 0", 0)
+
+ # < > <= >= have greater precedences than == !=
+ # Test >= and ==
+ test_expression("1 == 3 >= 3 + 1", 0)
+ test_expression("== 1 >= 3 + 3 1", 0)
+ test_expression("0 == 3 >= 3 + 1", 1)
+ test_expression("== 0 >= 3 + 3 1", 1)
+ test_expression("1 == 3 >= 3 - 1", 1)
+ test_expression("== 1 >= 3 - 3 1", 1)
+ test_expression("0 == 3 >= 3 - 1", 0)
+ test_expression("== 0 >= 3 - 3 1", 0)
+ test_expression("1 == 3 >= 3 + 0", 1)
+ test_expression("== 1 >= 3 + 3 0", 1)
+ test_expression("0 == 3 >= 3 + 0", 0)
+ test_expression("== 0 >= 3 + 3 0", 0)
+
+ # < > <= >= have greater precedences than == !=
+ # Test < and !=
+ test_expression("1 != 3 < 3 + 1", 0)
+ test_expression("!= 1 < 3 + 3 1", 0)
+ test_expression("0 != 3 < 3 + 1", 1)
+ test_expression("!= 0 < 3 + 3 1", 1)
+ test_expression("1 != 3 < 3 - 1", 1)
+ test_expression("!= 1 < 3 - 3 1", 1)
+ test_expression("0 != 3 < 3 - 1", 0)
+ test_expression("!= 0 < 3 - 3 1", 0)
+ test_expression("1 != 3 < 3 + 0", 1)
+ test_expression("!= 1 < 3 + 3 0", 1)
+ test_expression("0 != 3 < 3 + 0", 0)
+ test_expression("!= 0 < 3 + 3 0", 0)
+
+ # < > <= >= have greater precedences than == !=
+ # Test > and !=
+ test_expression("1 != 3 > 3 + 1", 1)
+ test_expression("!= 1 > 3 + 3 1", 1)
+ test_expression("0 != 3 > 3 + 1", 0)
+ test_expression("!= 0 > 3 + 3 1", 0)
+ test_expression("1 != 3 > 3 - 1", 0)
+ test_expression("!= 1 > 3 - 3 1", 0)
+ test_expression("0 != 3 > 3 - 1", 1)
+ test_expression("!= 0 > 3 - 3 1", 1)
+ test_expression("1 != 3 > 3 + 0", 1)
+ test_expression("!= 1 > 3 + 3 0", 1)
+ test_expression("0 != 3 > 3 + 0", 0)
+ test_expression("!= 0 > 3 + 3 0", 0)
+
+ # < > <= >= have greater precedences than == !=
+ # Test <= and !=
+ test_expression("1 != 3 <= 3 + 1", 0)
+ test_expression("!= 1 <= 3 + 3 1", 0)
+ test_expression("0 != 3 <= 3 + 1", 1)
+ test_expression("!= 0 <= 3 + 3 1", 1)
+ test_expression("1 != 3 <= 3 - 1", 1)
+ test_expression("!= 1 <= 3 - 3 1", 1)
+ test_expression("0 != 3 <= 3 - 1", 0)
+ test_expression("!= 0 <= 3 - 3 1", 0)
+ test_expression("1 != 3 <= 3 + 0", 0)
+ test_expression("!= 1 <= 3 + 3 0", 0)
+ test_expression("0 != 3 <= 3 + 0", 1)
+ test_expression("!= 0 <= 3 + 3 0", 1)
+
+ # < > <= >= have greater precedences than == !=
+ # Test >= and !=
+ test_expression("1 != 3 >= 3 + 1", 1)
+ test_expression("!= 1 >= 3 + 3 1", 1)
+ test_expression("0 != 3 >= 3 + 1", 0)
+ test_expression("!= 0 >= 3 + 3 1", 0)
+ test_expression("1 != 3 >= 3 - 1", 0)
+ test_expression("!= 1 >= 3 - 3 1", 0)
+ test_expression("0 != 3 >= 3 - 1", 1)
+ test_expression("!= 0 >= 3 - 3 1", 1)
+ test_expression("1 != 3 >= 3 + 0", 0)
+ test_expression("!= 1 >= 3 + 3 0", 0)
+ test_expression("0 != 3 >= 3 + 0", 1)
+ test_expression("!= 0 >= 3 + 3 0", 1)
+
+ # == != have higher precedences than &
+ # 3: 0011
+ # 6: 0110
+ # 5: 0101
+ # 6 & 5: 4
+ # 5 & 3: 1
+ test_expression("6 & 5 == 4", 0)
+ test_expression("& 6 == 5 4", 0)
+ test_expression("4 == 5 & 6", 0)
+ test_expression("& 4 == 5 6", 0)
+ test_expression("6 & 5 != 3", 0)
+ test_expression("& 6 != 5 3", 0)
+ test_expression("3 != 5 & 6", 0)
+ test_expression("& != 3 5 6", 0)
+ test_expression("6 & 5 != 4", 0)
+ test_expression("& 6 != 5 4", 0)
+ test_expression("4 != 5 & 6", 0)
+ test_expression("& != 4 5 6", 0)
+ test_expression("3 & 5 != 1", 1)
+ test_expression("& != 3 5 1", 1)
+ test_expression("1 != 5 & 3", 1)
+ test_expression("& != 1 5 3", 1)
+ test_expression("3 & 2 != 2", 0)
+ test_expression("& 3 != 2 2", 0)
+ test_expression("2 != 2 & 3", 0)
+ test_expression("& != 2 2 3", 0)
+
+ # & has higher precedence than ^
+ # 7: 0111
+ # 6: 0110
+ # 2: 0010
+ # 6 ^ 7: 1
+ # 3 & 6: 2
+ # 2 ^ 1: 3
+ # 2 ^ 7: 5
+ test_expression("3 & 6 ^ 7", 5)
+ test_expression("^ & 3 6 7", 5)
+ test_expression("6 & 3 ^ 7", 5)
+ test_expression("^ & 6 3 7", 5)
+ test_expression("7 ^ 6 & 3", 5)
+ test_expression("^ 7 & 6 3", 5)
+
+ # ^ has higher precedence than |
+ # 5: 0101
+ # 6: 0110
+ # 4: 0100
+ # 5 ^ 6: 3
+ # 3 | 4: 7
+ # 6 | 4: 6
+ # 6 ^ 7: 1
+ test_expression("5 ^ 6 | 4", 7)
+ test_expression("| ^ 5 6 4", 7)
+ test_expression("6 ^ 5 | 4", 7)
+ test_expression("| ^ 6 5 4", 7)
+ test_expression("4 | 5 ^ 6", 7)
+ test_expression("| 4 ^ 5 6", 7)
+ test_expression("4 | 6 ^ 5", 7)
+ test_expression("| 4 ^ 6 5", 7)
+
+ # | has higher precedence than &&
+ # 5 | 0: 5
+ # 5 && 4: 1
+ test_expression("5 | 0 && 4", 1)
+ test_expression("&& | 5 0 4", 1)
+ test_expression("0 | 5 && 4", 1)
+ test_expression("&& | 0 5 4", 1)
+ test_expression("4 && 0 | 5", 1)
+ test_expression("&& 4 | 0 5", 1)
+ test_expression("4 && 5 | 0", 1)
+ test_expression("&& 4 | 5 0", 1)
+
+ # && has higher precedence than ||
+ test_expression("0 && 1 || 1", 1)
+ test_expression("|| && 0 1 1", 1)
+ test_expression("1 && 0 || 1", 1)
+ test_expression("|| && 1 0 1", 1)
+ test_expression("1 || 0 && 1", 1)
+ test_expression("|| 1 && 0 1", 1)
+ test_expression("1 || 1 && 0", 1)
+ test_expression("|| 1 && 1 0", 1)
+
+ # Precedence sequence:
+ # 9: * / %
+ # 8: + -
+ # 7: >> <<
+ # 6: < > <= >=
+ # 5: == !=
+ # 4: &
+ # 3: ^
+ # 2: |
+ # 1: &&
+ # 0: ||
+ test_expression("1 + 2 * 3 - 1", 6)
+ test_expression("- + 1 * 2 3 1", 6)
+ test_expression("2 << 1 + 2 * 3 - 1", 128)
+ test_expression("<< 2 - + 1 * 2 3 1", 128)
+ test_expression("2 << 1 + 2 * 3 - 1 >> 5", 4)
+ test_expression(">> << 2 - + 1 * 2 3 1 5", 4)
+ test_expression("3 < 2 << 1 + 2 * 3 - 1 >> 5", 1)
+ test_expression("< 3 >> << 2 - + 1 * 2 3 1 5", 1)
+ test_expression("3 < 2 << 1 + 2 * 3 - 1 >> 5 > 1", 0)
+ test_expression("> < 3 >> << 2 - + 1 * 2 3 1 5 1", 0)
+ test_expression("3 < 2 << 1 + 2 * 3 - 1 >> 5 > 1 <= 0", 1)
+ test_expression("<= > < 3 >> << 2 - + 1 * 2 3 1 5 1 0", 1)
+ test_expression("3 < 2 << 1 + 2 * 3 - 1 >> 5 > 1 <= 0 >= 100", 0)
+ test_expression(">= <= > < 3 >> << 2 - + 1 * 2 3 1 5 1 0 100", 0)
+ test_expression("0 == 3 < 2 << 1 + 2 * 3 - 1 >> 5 > 1 <= 0 >= 100", 1)
+ test_expression("== 0 >= <= > < 3 >> << 2 - + 1 * 2 3 1 5 1 0 100", 1)
+ test_expression("0 == 3 < 2 << 1 + 2 * 3 - 1 >> 5 > 1 <= 0 >= 100 != 0", 1)
+ test_expression("!= == 0 >= <= > < 3 >> << 2 - + 1 * 2 3 1 5 1 0 100 0", 1)
+ test_expression("7 & 0 == 3 < 2 << 1 + 2 * 3 - 1 >> 5 > 1 <= 0 >= 100 != 0", 1)
+ test_expression("& 7 != == 0 >= <= > < 3 >> << 2 - + 1 * 2 3 1 5 1 0 100 0", 1)
+ test_expression("3 ^ 7 & 0 == 3 < 2 << 1 + 2 * 3 - 1 >> 5 > 1 <= 0 >= 100 != 0", 2)
+ test_expression("^ 3 & 7 != == 0 >= <= > < 3 >> << 2 - + 1 * 2 3 1 5 1 0 100 0", 2)
+ test_expression("5 | 3 ^ 7 & 0 == 3 < 2 << 1 + 2 * 3 - 1 >> 5 > 1 <= 0 >= 100 != 0", 7)
+ test_expression("| 5 ^ 3 & 7 != == 0 >= <= > < 3 >> << 2 - + 1 * 2 3 1 5 1 0 100 0", 7)
+ test_expression("0 && 5 | 3 ^ 7 & 0 == 3 < 2 << 1 + 2 * 3 - 1 >> 5 > 1 <= 0 >= 100 != 0", 0)
+ test_expression("&& 0 | 5 ^ 3 & 7 != == 0 >= <= > < 3 >> << 2 - + 1 * 2 3 1 5 1 0 100 0", 0)
+ test_expression("4 || 0 && 5 | 3 ^ 7 & 0 == 3 < 2 << 1 + 2 * 3 - 1 >> 5 > 1 <= 0 >= 100 != 0", 1)
+ test_expression("|| 4 && 0 | 5 ^ 3 & 7 != == 0 >= <= > < 3 >> << 2 - + 1 * 2 3 1 5 1 0 100 0", 1)
+
+ # Parenthesises
+ test_expression("(1 + 2)", 3)
+ test_expression("+ 1 2", 3)
+ test_expression("((1 + 2))", 3)
+ test_expression("+ 1 2", 3)
+ test_expression("(1 + 2) * 3", 9)
+ test_expression("* + 1 2 3", 9)
+ test_expression("(1 + 2) * (3 + 4)", 21)
+ test_expression("* + 1 2 + 3 4", 21)
+ test_expression("(1 + 2) * (((3 + 4)))", 21)
+ test_expression("* + 1 2 + 3 4", 21)
+ test_expression("((6 + 2) - (5 - 3)) * (3 - 1)", 12)
+ test_expression("* - + 6 2 - 5 3 - 3 1", 12)
+ test_expression("((6 + 2) - (5 - 3)) * ((3 - 1) / (3 - 2 + 1))", 6)
+ test_expression("* - + 6 2 - 5 3 / - 3 1 + - 3 2 1", 6)
+
+ for attr in lipe_constant.LIPE_POLICY_ATTRIBUTES:
+ test_expression_no_evaluate(attr)
+
+ # Attributes
+ for value in [12345691, 0]:
+ for attr in lipe_constant.LIPE_POLICY_ATTRIBUTES:
+ if attr == "type":
+ # type bits are filtered by 00170000
+ expected_value = 00170000 & value
+ elif attr == "stripe_count":
+ # stripe count is 16 bit
+ value = 0xFFFF & value
+ expected_value = value
+ elif attr == "empty":
+ # empty is bool
+ value = int(bool(value))
+ expected_value = value
+ else:
+ expected_value = value
+ test_expression(attr, expected_value,
+ options="--%s %s" % (attr, value))
+
+ # Contants
+ # S_IFMT 00170000
+ # S_IFSOCK 0140000
+ # S_IFLNK 0120000
+ # S_IFREG 0100000
+ # S_IFBLK 0060000
+ # S_IFDIR 0040000
+ # S_IFCHR 0020000
+ # S_IFIFO 0010000
+ # S_ISUID 0004000
+ # S_ISGID 0002000
+ # S_ISVTX 0001000
+ test_expression("S_IFMT", 00170000)
+ test_expression("S_IFSOCK", 0140000)
+ test_expression("S_IFLNK", 0120000)
+ test_expression("S_IFREG", 0100000)
+ test_expression("S_IFBLK", 0060000)
+ test_expression("S_IFDIR", 0040000)
+ test_expression("S_IFCHR", 0020000)
+ test_expression("S_IFIFO", 0010000)
+ test_expression("S_ISUID", 0004000)
+ test_expression("S_ISGID", 0002000)
+ test_expression("S_ISVTX", 0001000)
+
+ # Attribute tests
+ test_expression("1", options="--atime", expect_retval=RETVAL_ARG_INVALID)
+ test_expression("1", options="--atime x", expect_retval=RETVAL_ATTR_VALUE_INVALID)
+ test_expression("1", options="--atime 9223372036854775808",
+ expect_retval=RETVAL_ATTR_VALUE_INVALID)
+ test_expression("1", options="--nonexist_attr 1", expect_retval=RETVAL_ATTR_NAME_INVALID)
+ test_expression("hsm_states", expect_retval=RETVAL_ATTR_NO_VALUE)
+ # hsm_states is 32 bit, value larger than 32 bit will overflow
+ test_expression("hsm_states", 4294967295, options="--hsm_states 4294967295")
+ # S_IFSOCK 0140000
+ # S_IFLNK 0120000
+ # "type" won't overwrite other bits of "mode", only the limited bits
+ test_expression("mode", 0140740, options="--mode 0120740 --type 0140000")
+ test_expression("mode", 0140740, options="--mode 0110740 --type 0140036")
+ test_expression("sys_time", 1550310180000, options="--sys_time 1550310180000")
+
+ for xtime in ["atime", "ctime", "mtime"]:
+ test_expression(xtime, expect_retval=RETVAL_ATTR_NO_VALUE)
+ test_expression(xtime, 9223372036854775807, options="--%s 9223372036854775807" % xtime)
+ test_expression(xtime, 9223372036854775807, options="--%s 9223372036854775807" % xtime)
+ test_expression(xtime, expect_retval=RETVAL_ATTR_VALUE_INVALID,
+ options="--%s 9223372036854775808" % xtime)
+ # Same attribute will overwrite the previous ones
+ test_expression(xtime, 1550202026000,
+ options="--%s 9223372036854775807 --%s 1550202026000" % (xtime, xtime))
+
+ # 1 hour: 3600000
+ # 1 day: 86400000
+ # 100 days: 8640000000
+ # 90 days: 7776000000
+ # 89 days: 7689600000
+ find_xtime_10days = (lipe_find.LipeFindXtime.LFX_EXPRESSION_INFIX %
+ (xtime, 10, "days", xtime, 10, "days"))
+ find_xtime_10days_prefix = (lipe_find.LipeFindXtime.LFX_EXPRESSION_PREFIX %
+ (xtime, 10, "days", xtime, 10, "days"))
+ test_expression(find_xtime_10days, 0,
+ options="--sys_time 8640000000 --%s 7776000001" % xtime)
+ test_expression(find_xtime_10days_prefix, 0,
+ options="--sys_time 8640000000 --%s 7776000001" % xtime)
+ test_expression(find_xtime_10days, 1,
+ options="--sys_time 8640000000 --%s 7776000000" % xtime)
+ test_expression(find_xtime_10days_prefix, 1,
+ options="--sys_time 8640000000 --%s 7776000000" % xtime)
+ test_expression(find_xtime_10days, 1,
+ options="--sys_time 8640000000 --%s 7775999999" % xtime)
+ test_expression(find_xtime_10days_prefix, 1,
+ options="--sys_time 8640000000 --%s 7775999999" % xtime)
+ test_expression(find_xtime_10days, 0,
+ options="--sys_time 8640000000 --%s 7689600000" % xtime)
+ test_expression(find_xtime_10days_prefix, 0,
+ options="--sys_time 8640000000 --%s 7689600000" % xtime)
+ test_expression(find_xtime_10days, 1,
+ options="--sys_time 8640000000 --%s 7689600001" % xtime)
+ test_expression(find_xtime_10days_prefix, 1,
+ options="--sys_time 8640000000 --%s 7689600001" % xtime)
+
+ find_xtime_greater_10days = (lipe_find.LipeFindXtime.LFX_GREATER_EXPRESSION_INFIX %
+ (xtime, 10, "days"))
+ find_xtime_greater_10days_prefix = (lipe_find.LipeFindXtime.LFX_GREATER_EXPRESSION_PREFIX %
+ (xtime, 10, "days"))
+ test_expression(find_xtime_greater_10days, 0,
+ options="--sys_time 8640000000 --%s 7776000001" % xtime)
+ test_expression(find_xtime_greater_10days_prefix, 0,
+ options="--sys_time 8640000000 --%s 7776000001" % xtime)
+ test_expression(find_xtime_greater_10days, 0,
+ options="--sys_time 8640000000 --%s 7776000000" % xtime)
+ test_expression(find_xtime_greater_10days_prefix, 0,
+ options="--sys_time 8640000000 --%s 7776000000" % xtime)
+ test_expression(find_xtime_greater_10days, 0,
+ options="--sys_time 8640000000 --%s 7775999999" % xtime)
+ test_expression(find_xtime_greater_10days_prefix, 0,
+ options="--sys_time 8640000000 --%s 7775999999" % xtime)
+ test_expression(find_xtime_greater_10days, 0,
+ options="--sys_time 8640000000 --%s 7689600000" % xtime)
+ test_expression(find_xtime_greater_10days_prefix, 0,
+ options="--sys_time 8640000000 --%s 7689600000" % xtime)
+ test_expression(find_xtime_greater_10days, 0,
+ options="--sys_time 8640000000 --%s 7689600001" % xtime)
+ test_expression(find_xtime_greater_10days_prefix, 0,
+ options="--sys_time 8640000000 --%s 7689600001" % xtime)
+ test_expression(find_xtime_greater_10days, 1,
+ options="--sys_time 8640000000 --%s 7689599999" % xtime)
+ test_expression(find_xtime_greater_10days_prefix, 1,
+ options="--sys_time 8640000000 --%s 7689599999" % xtime)
+ test_expression(find_xtime_greater_10days, 1,
+ options="--sys_time 8640000000 --%s 0" % xtime)
+ test_expression(find_xtime_greater_10days_prefix, 1,
+ options="--sys_time 8640000000 --%s 0" % xtime)
+
+ find_xtime_less_10days = (lipe_find.LipeFindXtime.LFX_LESS_EXPRESSION_INFIX %
+ (xtime, 10, "days"))
+ find_xtime_less_10days_prefix = (lipe_find.LipeFindXtime.LFX_LESS_EXPRESSION_PREFIX %
+ (xtime, 10, "days"))
+ test_expression(find_xtime_less_10days, 0,
+ options="--sys_time 8640000000 --%s 7776000000" % xtime)
+ test_expression(find_xtime_less_10days_prefix, 0,
+ options="--sys_time 8640000000 --%s 7776000000" % xtime)
+ test_expression(find_xtime_less_10days, 0,
+ options="--sys_time 8640000000 --%s 7775999999" % xtime)
+ test_expression(find_xtime_less_10days_prefix, 0,
+ options="--sys_time 8640000000 --%s 7775999999" % xtime)
+ test_expression(find_xtime_less_10days, 1,
+ options="--sys_time 8640000000 --%s 7776000001" % xtime)
+ test_expression(find_xtime_less_10days_prefix, 1,
+ options="--sys_time 8640000000 --%s 7776000001" % xtime)
+ test_expression(find_xtime_less_10days, 1,
+ options="--sys_time 8640000000 --%s 8640000000" % xtime)
+ test_expression(find_xtime_less_10days_prefix, 1,
+ options="--sys_time 8640000000 --%s 8640000000" % xtime)
+ test_expression(find_xtime_less_10days, 1,
+ options="--sys_time 8640000000 --%s 8640000001" % xtime)
+ test_expression(find_xtime_less_10days_prefix, 1,
+ options="--sys_time 8640000000 --%s 8640000001" % xtime)
+ test_expression(find_xtime_less_10days, 0,
+ options="--sys_time 8640000000 --%s 7689600000" % xtime)
+ test_expression(find_xtime_less_10days_prefix, 0,
+ options="--sys_time 8640000000 --%s 7689600000" % xtime)
+ test_expression(find_xtime_less_10days, 0,
+ options="--sys_time 8640000000 --%s 7689600001" % xtime)
+ test_expression(find_xtime_less_10days_prefix, 0,
+ options="--sys_time 8640000000 --%s 7689600001" % xtime)
+ test_expression(find_xtime_less_10days, 0,
+ options="--sys_time 8640000000 --%s 7689599999" % xtime)
+ test_expression(find_xtime_less_10days_prefix, 0,
+ options="--sys_time 8640000000 --%s 7689599999" % xtime)
+
+ test_size_expressions(attribute="size")
+ test_size_expressions(attribute="blocks")
+ test_size_expressions(attribute="stripe_count", has_unit=False,
+ bits=16)
+ test_size_expressions(attribute="projid", has_unit=False)
+ test_size_expressions(attribute="stripe_size", has_unit=False,
+ bits=32)
+ test_size_expressions(attribute="comp_count", has_unit=False,
+ bits=32)
+
+
+def main():
+ """
+ Run tests of expression evaluation, if exception, return -1
+ """
+ main_with_exception()
+ if len(sys.argv) > 1:
+ fd = open(sys.argv[1], 'w')
+ fd.close()
+ return 0
--- /dev/null
- rc = lipe_scan(instance, &policy, &result, NULL,
- NULL, opt.o_scan_threads, "lpcc_purge",
- NULL, true, false, &ldd_err);
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <json-c/json.h>
+#include <libgen.h>
+#include <linux/lustre/lustre_idl.h>
+#include <lustre/lustreapi.h>
+#include <lustre/lustre_user.h>
+#include <pthread.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <sys/file.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/vfs.h>
+#include <unistd.h>
+
+#include "debug.h"
+#include "lipe_object_attrs.h"
+#include "policy.h"
+
+
+#define DEF_HIGH_USAGE 90
+#define DEF_LOW_USAGE 75
+#define DEF_INTERVAL 5
+#define DEF_SCAN_THREADS 1
+#define DEF_CANDIDATE_NUM (128 * 1024)
+#define MIN_CANDIDATE_NUM 1024
+#define MAX_CANDIDATE_NUM (100 * 1024 * 1024)
+#define DEF_MAX_SCAN_SECS 30
+#define MIN_MAX_SCAN_SECS 5
+#define MAX_MAX_SCAN_SECS 300
+
+#define MAX_ATIME_FID_LEN 128
+
+#define OPT_DRY_RUN 1
+#define OPT_CANDIDATE_NUM 2
+#define OPT_CLEAR_HASHDIR 3
+#define OPT_LOG_LEVEL 4
+#define OPT_MAX_SCAN_SECS 5
+#define OPT_PIDFILE 6
+
+struct lpcc_purge_options {
+ char *o_cache;
+ char *o_mount;
+ unsigned int o_rwid;
+
+ double o_high_usage;
+ double o_low_usage;
+
+ int o_interval;
+ int o_scan_threads;
+ int o_candidate_num;
+ int o_max_scan_secs;
+
+ char *o_dumpfile;
+ char *o_pidfile;
+ bool o_dry_run;
+ bool o_clear_hashdir;
+};
+static struct lpcc_purge_options opt = {
+ .o_rwid = -1,
+ .o_high_usage = DEF_HIGH_USAGE,
+ .o_low_usage = DEF_LOW_USAGE,
+ .o_interval = DEF_INTERVAL,
+ .o_scan_threads = DEF_SCAN_THREADS,
+ .o_candidate_num = DEF_CANDIDATE_NUM,
+ .o_max_scan_secs = DEF_MAX_SCAN_SECS,
+ .o_dry_run = false,
+ .o_clear_hashdir = false,
+};
+
+bool exit_flag = false;
+struct lpcc_purge_stats {
+ double s_start_usage;
+ uint64_t s_scan_times;
+ time_t s_start_time, s_end_time;
+ time_t s_scan_start_sec;
+
+ pthread_mutex_t s_lock;
+ uint64_t s_total_purged_objs;
+ uint64_t s_total_failed_objs;
+ uint64_t s_scanned_objs; /* scanned objects in current scanning */
+ uint64_t s_purged_objs; /* purged objects in current scanning */
+};
+
+static struct lpcc_purge_stats stats = {
+ .s_start_usage = -1,
+ .s_lock = PTHREAD_MUTEX_INITIALIZER,
+};
+
+static struct lipe_instance *instance = NULL;
+
+struct lpcc_purge_candidate {
+ uint64_t c_atime_ms;
+ struct lu_fid c_fid;
+ char c_path[0];
+};
+
+struct lpcc_purge_candidate_set {
+ struct lpcc_purge_candidate ** cs_arr;
+ int cs_capacity; /* the total capacity of array */
+ int cs_count; /* the number of valid elements in array */
+ pthread_mutex_t cs_lock;
+};
+static struct lpcc_purge_candidate_set candidate_set;
+
+struct lpcc_purge_candidate *lpcc_purge_candidate_new(
+ uint64_t atime_ms, struct lu_fid fid, const char *path)
+{
+ struct lpcc_purge_candidate *candidate =
+ (struct lpcc_purge_candidate *)malloc(
+ sizeof(struct lpcc_purge_candidate) + strlen(path) + 1);
+
+ if (candidate == NULL) {
+ llapi_error(LLAPI_MSG_FATAL, errno,
+ "cannot allocate memory for candidate!");
+ exit(1);
+ }
+
+ candidate->c_atime_ms = atime_ms;
+ candidate->c_fid = fid;
+ strcpy(candidate->c_path, path);
+
+ return candidate;
+}
+
+void lpcc_purge_candidate_destroy(struct lpcc_purge_candidate *candidate)
+{
+ free(candidate);
+}
+
+static int _compare_candidate(const void *p1, const void *p2)
+{
+ const struct lpcc_purge_candidate *c1 = *(const struct lpcc_purge_candidate **)(p1);
+ const struct lpcc_purge_candidate *c2 = *(const struct lpcc_purge_candidate **)(p2);
+
+ if (c1->c_atime_ms < c2->c_atime_ms)
+ return -1;
+ if (c1->c_atime_ms > c2->c_atime_ms)
+ return 1;
+ return 0;
+}
+
+static void lpcc_purge_candidate_set_init(int capacity)
+{
+ candidate_set.cs_arr = calloc(capacity, sizeof(candidate_set.cs_arr[0]));
+ if (candidate_set.cs_arr == NULL) {
+ llapi_error(LLAPI_MSG_FATAL, errno,
+ "cannot allocate memory for candidate set!");
+ exit(1);
+ }
+ candidate_set.cs_capacity = capacity;
+ candidate_set.cs_count = 0;
+ pthread_mutex_init(&candidate_set.cs_lock, NULL);
+}
+
+static void lpcc_purge_candidate_set_destroy(void)
+{
+ int i;
+ for (i = 0; i < candidate_set.cs_count; i++)
+ lpcc_purge_candidate_destroy(candidate_set.cs_arr[i]);
+
+ free(candidate_set.cs_arr);
+}
+
+static void lpcc_purge_candidate_set_clear(void)
+{
+ int i;
+ for (i = 0; i < candidate_set.cs_count; i++)
+ lpcc_purge_candidate_destroy(candidate_set.cs_arr[i]);
+
+ candidate_set.cs_count = 0;
+}
+
+static void lpcc_purge_candidate_set_append(
+ struct lpcc_purge_candidate *candidate)
+{
+ if (candidate_set.cs_count >= candidate_set.cs_capacity) {
+ llapi_error(LLAPI_MSG_FATAL, EFAULT,
+ "Out of boundary of array");
+ exit(1);
+ }
+
+ candidate_set.cs_arr[candidate_set.cs_count++] = candidate;
+}
+
+void lpcc_purge_candidate_set_sort(void)
+{
+ qsort(candidate_set.cs_arr, candidate_set.cs_count,
+ sizeof(candidate_set.cs_arr[0]), &_compare_candidate);
+}
+
+static void lpcc_purge_null_handler(int signal)
+{
+}
+
+static void lpcc_purge_sigint_handler(int signal)
+{
+ psignal(signal, "exiting");
+ _exit(0);
+}
+
+/**
+ * Dump:
+ * - config
+ * - stats
+ */
+static void lpcc_purge_dump_config_stats(FILE *f)
+{
+ int rc;
+ char buff[64];
+ time_t curr;
+
+ json_object *j_all = json_object_new_object();
+
+ curr = time(NULL);
+ ctime_r(&curr, buff);
+ json_object *j_curr_time = json_object_new_string(buff);
+ json_object_object_add(j_all, "curr_time", j_curr_time);
+ rc = llapi_get_fsname(opt.o_mount, buff, sizeof(buff));
+ if (rc) {
+ llapi_error(LLAPI_MSG_FATAL, rc, "Cannot get fsname.");
+ exit(1);
+ }
+ json_object_object_add(j_all, "fsname", json_object_new_string(buff));
+
+ json_object *j_config = json_object_new_object();
+ json_object_object_add(j_config, "mount", json_object_new_string(opt.o_mount));
+ json_object_object_add(j_config, "cache", json_object_new_string(opt.o_cache));
+ json_object_object_add(j_config, "rwid", json_object_new_int64(opt.o_rwid));
+ json_object_object_add(j_config, "high_usage", json_object_new_double(opt.o_high_usage));
+ json_object_object_add(j_config, "low_usage", json_object_new_double(opt.o_low_usage));
+ json_object_object_add(j_config, "interval", json_object_new_int64(opt.o_interval));
+ json_object_object_add(j_config, "scan_threads", json_object_new_int64(opt.o_scan_threads));
+ json_object_object_add(j_config, "candidate_num", json_object_new_int64(opt.o_candidate_num));
+ json_object_object_add(j_config, "clear_hashdir", json_object_new_boolean(opt.o_clear_hashdir));
+ json_object_object_add(j_config, "max_scan_secs", json_object_new_int64(opt.o_max_scan_secs));
+ json_object_object_add(j_all, "config", j_config);
+
+ json_object *j_stats = json_object_new_object();
+ json_object_object_add(j_stats, "scan_times", json_object_new_int64(stats.s_scan_times));
+ json_object_object_add(j_stats, "total_purged_objs", json_object_new_int64(stats.s_total_purged_objs));
+ json_object_object_add(j_stats, "total_failed_objs", json_object_new_int64(stats.s_total_failed_objs));
+ if (stats.s_start_time != 0) {
+ ctime_r(&stats.s_start_time, buff);
+ json_object_object_add(j_stats, "start_time", json_object_new_string(buff));
+ json_object_object_add(j_stats, "start_usage", json_object_new_double(stats.s_start_usage));
+ }
+ if (stats.s_end_time != 0) {
+ ctime_r(&stats.s_end_time, buff);
+ json_object_object_add(j_stats, "end_time", json_object_new_string(buff));
+ }
+ json_object_object_add(j_stats, "scanned_objs", json_object_new_int64(stats.s_scanned_objs));
+ json_object_object_add(j_stats, "purged_objs", json_object_new_int64(stats.s_purged_objs));
+ json_object_object_add(j_all, "stats", j_stats);
+
+ const char *str = json_object_to_json_string_ext(j_all,
+ JSON_C_TO_STRING_PRETTY |
+ JSON_C_TO_STRING_SPACED |
+ JSON_C_TO_STRING_NOZERO);
+
+ fprintf(f, "%s\n", str);
+ fflush(f);
+
+ json_object_put(j_all);
+}
+
+static void lpcc_purge_usr1_handler(int sig)
+{
+ FILE *f = NULL;
+
+ f = fopen(opt.o_dumpfile, "w");
+ if (!f) {
+ llapi_printf(LLAPI_MSG_DEBUG, "cannot open dumpfile '%s'\n",
+ opt.o_dumpfile);
+ return;
+ };
+
+ lpcc_purge_dump_config_stats(f);
+
+ fclose(f);
+}
+
+static void usage(void)
+{
+ printf("Usage: %s [options]\n"
+ "\t-b, --debug, enable debugging output\n"
+ "\t --log-level={debug|info|normal|warn|error|fatal|off}, set log level (default: info)\n"
+ "\t-f, --config-file=FILE\n"
+ "\t-C, --cache=DIR root directory of PCC\n"
+ "\t-M, --mount=DIR local Lustre client's mountpoint\n"
+ "\t-A, --rwid, --roid=NUM the roid/rwid of PCC\n"
+ "\t-H, --high-usage=NUM %% of space or inode to start purging (default: %u)\n"
+ "\t-L, --low-usage=NUM %% of space or inode to stop purging (default: %u)\n"
+ "\t-i, --interval=NUM, seconds to next check (default: %u)\n"
+ "\t-t, --scan-threads=NUM scanning threads (default: %u)\n"
+ "\t --candidate-num=NUM, candidate number of approximate LRU (default: %d, min: %d, max: %d)\n"
+ "\t --max-scan-secs, max seconds to scan continously before purging (default: %d, min: %d, max: %d)\n"
+ "\t-w, --dump=FILE, dump stats to FILE when signal USR1 is recieved (default: /var/run/lpcc_purge-RWID.stats)\n"
+ "\t --pidfile=FILE, the pidfile name (default: /var/run/lpcc_purge-RWID.pid)\n"
+ "\t --clear-hashdir, clear empty hash dir after detaching file\n"
+ "\t --dry-run, scan once but do not detach file really\n"
+ "\t-h, --help, print this help message\n",
+
+ program_invocation_short_name,
+ DEF_HIGH_USAGE,
+ DEF_LOW_USAGE,
+ DEF_INTERVAL,
+ DEF_SCAN_THREADS,
+ DEF_CANDIDATE_NUM, MIN_CANDIDATE_NUM, MAX_CANDIDATE_NUM,
+ DEF_MAX_SCAN_SECS, MIN_MAX_SCAN_SECS, MAX_MAX_SCAN_SECS
+ );
+}
+
+static struct option long_options[] = {
+ { "debug", no_argument, NULL, 'b'},
+ { "config-file", required_argument, NULL, 'f'},
+ { "log-level", required_argument, NULL, OPT_LOG_LEVEL},
+ { "cache", required_argument, NULL, 'C'},
+ { "mount", required_argument, NULL, 'M'},
+ { "rwid", required_argument, NULL, 'A'},
+ { "roid", required_argument, NULL, 'A'},
+ { "high-usage", required_argument, NULL, 'H'},
+ { "low-usage", required_argument, NULL, 'L'},
+ { "interval", required_argument, NULL, 'i'},
+ { "scan-threads", required_argument, NULL, 't'},
+ { "dry-run", no_argument, NULL, OPT_DRY_RUN},
+ { "candidate-num", required_argument, NULL, OPT_CANDIDATE_NUM},
+ { "dump", required_argument, NULL, 'w'},
+ { "pidfile", required_argument, NULL, OPT_PIDFILE},
+ { "clear-hashdir", no_argument, NULL, OPT_CLEAR_HASHDIR},
+ { "max-scan-secs", required_argument, NULL, OPT_MAX_SCAN_SECS},
+ { "help", no_argument, NULL, 'h' },
+ { NULL }
+};
+
+static struct option *lpcc_purge_keyword_lookup(const char *keyword)
+{
+ int i;
+ char keyword2[PATH_MAX];
+
+ for (i = 0; i <= strlen(keyword) && i < PATH_MAX; i++) {
+ if (keyword[i] == '_')
+ keyword2[i] = '-';
+ else
+ keyword2[i] = keyword[i];
+ }
+
+ i = 0;
+ while (long_options[i].name) {
+ if (strcmp(keyword2, long_options[i].name) == 0)
+ return long_options + i;
+ i++;
+ }
+ return NULL;
+}
+
+static void lpcc_purge_process_opt(int c, char *optarg);
+
+static void load_config(const char *path)
+{
+ size_t len = 0;
+ char *buf = NULL;
+
+ FILE *f = fopen(path, "r");
+ if (!f) {
+ llapi_error(LLAPI_MSG_FATAL, errno,
+ "cannot open config file '%s'", path);
+ exit(1);
+ }
+
+ while (!feof(f)) {
+ struct option *opt;
+ char *s, *t;
+ len = PATH_MAX;
+
+ if (getline(&buf, &len, f) <= 0)
+ break;
+ s = buf;
+ while (isspace(*s))
+ s++;
+ t = strchr(s, '#');
+ if (t)
+ *t = '\0';
+ if (*s == '#')
+ continue;
+ t = strsep(&s, "=\n");
+ if (!t || *t == 0)
+ continue;
+ opt = lpcc_purge_keyword_lookup(t);
+ if (!opt) {
+ llapi_error(LLAPI_MSG_ERROR, EINVAL,
+ "unknown tunable '%s'", t);
+ continue;
+ }
+ if (opt->val == 'f') {
+ /* you shall not pass! */
+ continue;
+ }
+
+ if (opt->has_arg == required_argument ||
+ opt->has_arg == optional_argument) {
+ optarg = strsep(&s, "\n ");
+ if (!optarg &&
+ opt->has_arg == required_argument) {
+ llapi_error(LLAPI_MSG_FATAL, EINVAL,
+ "no argument for '%s'", t);
+ exit(1);
+ }
+ }
+ else {
+ optarg = NULL;
+ }
+ llapi_printf(LLAPI_MSG_DEBUG, "conf: %s %s\n", t,
+ optarg ? optarg : "");
+ lpcc_purge_process_opt(opt->val, optarg);
+ }
+
+ free(buf);
+ fclose(f);
+}
+
+static const char *log_level_strs[] = {
+ [LLAPI_MSG_OFF] = "off",
+ [LLAPI_MSG_FATAL] = "fatal",
+ [LLAPI_MSG_ERROR] = "error",
+ [LLAPI_MSG_WARN] = "warn",
+ [LLAPI_MSG_NORMAL] = "normal",
+ [LLAPI_MSG_INFO] = "info",
+ [LLAPI_MSG_DEBUG] = "debug",
+};
+
+static enum llapi_message_level parse_log_level(const char *level_str)
+{
+ enum llapi_message_level level = LLAPI_MSG_INFO;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(log_level_strs); i++) {
+ if (strcasecmp(log_level_strs[i], level_str) == 0) {
+ level = i;
+ break;
+ }
+ }
+
+ if (i == sizeof(log_level_strs) / sizeof(char *)) {
+ llapi_error(LLAPI_MSG_WARN, -EINVAL,
+ "Unrecognized log level string '%s'",
+ optarg);
+ }
+
+ return level;
+}
+
+static void parse_mountpoint(const char *name)
+{
+ int rc;
+ struct statfs statbuf;
+
+ rc = statfs(name, &statbuf);
+ if (rc != 0) {
+ llapi_error(LLAPI_MSG_FATAL, -rc,
+ "cannot statfs '%s'", name);
+ exit(1);
+ }
+ if (statbuf.f_type != LL_SUPER_MAGIC) {
+ llapi_error(LLAPI_MSG_FATAL, EINVAL,
+ "'%s' is not a lustre mount point", name);
+ exit(1);
+ }
+}
+
+static void parse_cache_dir(const char *name)
+{
+ struct stat stat1;
+ int rc;
+
+ rc = stat(name, &stat1);
+ if (rc) {
+ llapi_error(LLAPI_MSG_FATAL, errno,
+ "cannot stat cache dir '%s'", name);
+ exit(1);
+ }
+
+ if (!S_ISDIR(stat1.st_mode)) {
+ llapi_error(LLAPI_MSG_FATAL, errno,
+ "cache path '%s' is not a directory", name);
+ exit(1);
+ }
+}
+
+static void lpcc_purge_process_opt(int c, char *optarg)
+{
+ long value;
+ char *endptr = NULL;
+ enum llapi_message_level log_level;
+
+ switch(c) {
+ case 'f':
+ load_config(optarg);
+ break;
+ case 'b':
+ llapi_msg_set_level(LLAPI_MSG_MAX);
+ break;
+ case OPT_LOG_LEVEL:
+ log_level = parse_log_level(optarg);
+ llapi_printf(LLAPI_MSG_INFO, "set log level: %s\n",
+ log_level_strs[log_level]);
+ llapi_msg_set_level(log_level);
+
+ break;
+ case 'C':
+ parse_cache_dir(optarg);
+ opt.o_cache = strdup(optarg);
+ break;
+ case 'M':
+ parse_mountpoint(optarg);
+ opt.o_mount = strdup(optarg);
+ break;
+ case 'A':
+ value = strtol(optarg, &endptr, 10);
+ if (*endptr != '\0' || value <= 0) {
+ llapi_error(LLAPI_MSG_FATAL, -EINVAL,
+ "invalid roid/rwid: '%s'",
+ optarg);
+ exit(1);
+ }
+ opt.o_rwid = value;
+ break;
+ case 'H':
+ value = strtol(optarg, &endptr, 10);
+ if (*endptr != '\0' || value < 1 || value > 99) {
+ llapi_error(LLAPI_MSG_FATAL, -EINVAL,
+ "invalid high watermark: '%s'",
+ optarg);
+ exit(1);
+ }
+ opt.o_high_usage = value;
+ break;
+ case 'L':
+ value = strtol(optarg, &endptr, 10);
+ if (*endptr != '\0' || value < 1 || value > 99) {
+ llapi_error(LLAPI_MSG_FATAL, -EINVAL,
+ "invalid low watermark: '%s'",
+ optarg);
+ exit(1);
+ }
+ opt.o_low_usage = value;
+ break;
+ case 'i':
+ value = strtol(optarg, &endptr, 10);
+ if (*endptr != '\0' || value <= 0) {
+ llapi_error(LLAPI_MSG_FATAL, -EINVAL,
+ "invalid check interval: '%s'",
+ optarg);
+ exit(1);
+ }
+ opt.o_interval = value;
+ break;
+ case 't':
+ value = strtol(optarg, &endptr, 10);
+ if (*endptr != '\0' || value <= 0) {
+ llapi_error(LLAPI_MSG_FATAL, -EINVAL,
+ "invalid scan threads count: '%s'",
+ optarg);
+ exit(1);
+ }
+ opt.o_scan_threads = value;
+ break;
+ case 'w':
+ opt.o_dumpfile = strdup(optarg);
+ break;
+ case OPT_PIDFILE:
+ opt.o_pidfile = strdup(optarg);
+ break;
+ case OPT_DRY_RUN:
+ opt.o_dry_run = true;
+ break;
+ case OPT_CANDIDATE_NUM:
+ value = strtol(optarg, &endptr, 10);
+ if (*endptr != '\0' || value < MIN_CANDIDATE_NUM ||
+ value > MAX_CANDIDATE_NUM) {
+ llapi_error(LLAPI_MSG_FATAL, -EINVAL,
+ "invalid candidate number: '%s'",
+ optarg);
+ exit(1);
+ }
+ opt.o_candidate_num = value;
+ break;
+ case OPT_CLEAR_HASHDIR:
+ opt.o_clear_hashdir = true;
+ break;
+ case OPT_MAX_SCAN_SECS:
+ value = strtol(optarg, &endptr, 10);
+ if (*endptr != '\0' || value < MIN_MAX_SCAN_SECS ||
+ value > MAX_MAX_SCAN_SECS) {
+ llapi_error(LLAPI_MSG_FATAL, -EINVAL,
+ "invalid max_scan_secs: '%s'\n",
+ optarg);
+ exit(1);
+ }
+ opt.o_max_scan_secs = value;
+ break;
+ default:
+ llapi_error(LLAPI_MSG_FATAL, -EINVAL,
+ "invalid argument: '%s'",
+ optarg);
+ exit(1);
+ break;
+ }
+}
+
+static void lpcc_purge_parse_opts(int argc, char **argv)
+{
+ int c;
+
+ while ((c = getopt_long(argc, argv,
+ "bf:C:M:A:H:L:i:t:w:h",
+ long_options, NULL))
+ != EOF) {
+ switch(c) {
+ case '?':
+ /* Don't do further process if invalid option found */
+ fprintf(stderr,
+ "Try '%s --help' for more information.\n",
+ program_invocation_short_name);
+ exit(1);
+ case 'h':
+ usage();
+ exit(0);
+ default:
+ lpcc_purge_process_opt(c, optarg);
+ break;
+ }
+ }
+}
+
+void lpcc_purge_verify_opts(void)
+{
+ char buf[PATH_MAX];
+
+ if (opt.o_mount == NULL) {
+ llapi_error(LLAPI_MSG_FATAL, EINVAL,
+ "lustre mount point must be specified");
+ exit(1);
+ }
+ if (opt.o_cache == NULL) {
+ llapi_error(LLAPI_MSG_FATAL, EINVAL,
+ "pcc cache dir must be specified");
+ exit(1);
+ }
+ if (opt.o_rwid == -1) {
+ llapi_error(LLAPI_MSG_FATAL, EINVAL,
+ "roid/rwid must be specified");
+ exit(1);
+ }
+ if (opt.o_dumpfile == NULL) {
+ snprintf(buf, sizeof(buf), "/var/run/lpcc_purge-%d.stats", opt.o_rwid);
+ opt.o_dumpfile = strdup(buf);
+ }
+ if (opt.o_pidfile == NULL) {
+ snprintf(buf, sizeof(buf), "/var/run/lpcc_purge-%d.pid", opt.o_rwid);
+ opt.o_pidfile = strdup(buf);
+ }
+
+ /* check freehi > freelo */
+ if (opt.o_high_usage <= opt.o_low_usage) {
+ llapi_error(LLAPI_MSG_FATAL, EINVAL,
+ "high usage (%.1f) must be larger than low usage (%.1f)",
+ opt.o_high_usage, opt.o_low_usage);
+ exit(1);
+ }
+}
+
+/* It seems that lustre/utils/pid_file.c does not exist in RHEL7.x,
+ * so copy the function here
+ */
+static int create_pid_file(const char *path)
+{
+ char buf[3 * sizeof(long long) + 2];
+ size_t buf_len;
+ int fd = -1;
+ int rc2;
+
+ fd = open(path, O_RDWR|O_CREAT|O_CLOEXEC, 0600);
+ if (fd < 0) {
+ fprintf(stderr, "%s: cannot open '%s': %s\n",
+ program_invocation_short_name, path, strerror(errno));
+ return -1;
+ }
+
+ struct flock fl = {
+ .l_type = F_WRLCK,
+ .l_whence = SEEK_SET,
+ };
+
+ rc2 = fcntl(fd, F_SETLK, &fl);
+ if (rc2 < 0) {
+ fprintf(stderr, "%s: cannot lock '%s': %s\n",
+ program_invocation_short_name, path, strerror(errno));
+ goto out;
+ }
+
+ rc2 = ftruncate(fd, 0);
+ if (rc2 < 0) {
+ fprintf(stderr, "%s: cannot truncate '%s': %s\n",
+ program_invocation_short_name, path, strerror(errno));
+ goto out;
+ }
+
+ buf_len = snprintf(buf, sizeof(buf), "%lld\n", (long long)getpid());
+ rc2 = write(fd, buf, buf_len);
+ if (rc2 < 0) {
+ fprintf(stderr, "%s: cannot write '%s': %s\n",
+ program_invocation_short_name, path, strerror(errno));
+ goto out;
+ }
+out:
+ if (rc2 < 0 && !(fd < 0)) {
+ close(fd);
+ fd = -1;
+ }
+
+ return fd;
+}
+
+static void lpcc_purge_lock_pidfile(void)
+{
+ int fd;
+
+ fd = create_pid_file(opt.o_pidfile);
+ if (fd < 0) {
+ llapi_error(LLAPI_MSG_FATAL, errno,
+ "cannot create pidfile '%s'", opt.o_pidfile);
+ exit(1);
+ }
+ /* we keep the fd open to hold the flock,
+ it will be closed automatically when the process exits */
+}
+
+static double lpcc_purge_get_fs_usage(const char *fs)
+{
+ int rc;
+ double usage, i_usage;
+ struct statfs statfs_buf;
+
+ rc = statfs(opt.o_cache, &statfs_buf);
+ if (rc) {
+ llapi_error(LLAPI_MSG_FATAL, errno, "cannot statfs '%s'", fs);
+ exit(1);
+ }
+
+ usage = 100.0 * (statfs_buf.f_blocks - statfs_buf.f_bavail) / statfs_buf.f_blocks;
+ i_usage = 100.0 * (statfs_buf.f_files - statfs_buf.f_ffree) / statfs_buf.f_files;
+
+ return (usage > i_usage) ? usage : i_usage;
+}
+
+static void lpcc_purge_wait_for_scan(void)
+{
+ while (1) {
+ double usage = lpcc_purge_get_fs_usage(opt.o_cache);
+ llapi_printf(LLAPI_MSG_DEBUG, "usage: %.1f\n", usage);
+ if (usage >= opt.o_high_usage || opt.o_dry_run) {
+ stats.s_start_usage = usage;
+ break;
+ } else {
+ sleep(opt.o_interval);
+ }
+ }
+}
+
+static int clear_hash_dir(const char *path)
+{
+ int rc;
+ char dir[PATH_MAX], tmp[PATH_MAX];
+
+ strcpy(tmp, path);
+ strcpy(dir, dirname(tmp));
+
+ while (strlen(dir) > strlen(opt.o_cache)) {
+ rc = rmdir(dir);
+ if (rc) {
+ if (errno == ENOTEMPTY || errno == EBUSY) {
+ rc = 0;
+ }
+ else {
+ rc = -errno;
+ llapi_error(LLAPI_MSG_WARN, -rc,
+ "cannot remove hash dir: %s", dir);
+ }
+ break;
+ }
+ else {
+ strcpy(tmp, dir);
+ strcpy(dir, dirname(tmp));
+ }
+ }
+
+ return rc;
+}
+
+static int lpcc_purge_detach_by_fid(const char *mntpath,
+ const struct lu_fid *fid, const char *cache_file)
+{
+ int rc;
+ int fd;
+ struct lu_pcc_detach_fid detach;
+
+ fd = open(mntpath, O_DIRECTORY | O_RDONLY);
+ if (fd < 0) {
+ rc = -errno;
+ llapi_error(LLAPI_MSG_ERROR, rc, "cannot open root path: %s",
+ mntpath);
+ return rc;
+ }
+
+ detach.pccd_fid = *fid;
+ detach.pccd_flags = PCC_DETACH_FL_UNCACHE;
+ rc = ioctl(fd, LL_IOC_PCC_DETACH_BY_FID, &detach);
+ close(fd);
+
+ if (rc) {
+ /* skip */
+ llapi_error(LLAPI_MSG_DEBUG, rc,
+ "cannot detach fid: "DFID"", PFID(fid));
+ return rc;
+ }
+
+ if (detach.pccd_flags & PCC_DETACH_FL_CACHE_REMOVED) {
+ llapi_printf(LLAPI_MSG_DEBUG,
+ "Detach and remove the PCC cached fid: "DFID"\n",
+ PFID(fid));
+ }
+ else if (detach.pccd_flags & PCC_DETACH_FL_ATTACHING) {
+ llapi_printf(LLAPI_MSG_DEBUG,
+ "fid "DFID" is being attached, skip it", PFID(fid));
+ }
+ else {
+ llapi_printf(LLAPI_MSG_DEBUG,
+ "Remove non-cached file: %s flags: %X\n",
+ cache_file, detach.pccd_flags);
+ rc = unlink(cache_file);
+ if (rc < 0 && errno != ENOENT) {
+ rc = -errno;
+ llapi_error(LLAPI_MSG_ERROR, rc,
+ "Unlink %s failed", cache_file);
+ }
+
+ }
+
+ return rc;
+}
+
+static int lpcc_purge_detach_candidate(const char *mnt,
+ const struct lpcc_purge_candidate *candidate)
+{
+ int rc;
+ struct stat statbuf;
+ uint64_t atime_ms;
+
+ llapi_printf(LLAPI_MSG_DEBUG, "detach fid: "DFID"\n", PFID(&candidate->c_fid));
+
+ /* double confirm the atime. If it's changed, discard this entry */
+ rc = stat(candidate->c_path, &statbuf);
+ if (rc) {
+ rc = -errno;
+ llapi_error(LLAPI_MSG_WARN, -rc, "cannot stat file '%s'", candidate->c_path);
+ return rc;
+ }
+ atime_ms = statbuf.st_atim.tv_sec * 1000 + statbuf.st_atim.tv_nsec / 1000000;
+ if (candidate->c_atime_ms != atime_ms)
+ /* skip entirely */
+ return 0;
+
+ pthread_mutex_lock(&stats.s_lock);
+ stats.s_purged_objs++;
+ stats.s_total_purged_objs++;
+ pthread_mutex_unlock(&stats.s_lock);
+
+ if (opt.o_dry_run)
+ return rc;
+
+ rc = lpcc_purge_detach_by_fid(mnt, &candidate->c_fid, candidate->c_path);
+ if (rc) {
+ llapi_error(LLAPI_MSG_WARN, -rc, "cannot detach fid: "DFID,
+ PFID(&candidate->c_fid));
+ pthread_mutex_lock(&stats.s_lock);
+ stats.s_total_failed_objs++;
+ pthread_mutex_unlock(&stats.s_lock);
+ return rc;
+ }
+
+ if (opt.o_clear_hashdir)
+ rc = clear_hash_dir(candidate->c_path);
+
+ return 0;
+}
+
+static int lpcc_purge_scan_callback(struct lipe_instance *instance,
+ struct lipe_object *object,
+ struct lipe_object_attrs *attrs)
+{
+ int rc;
+ struct lu_fid fid;
+ char *path = NULL;
+ struct lpcc_purge_candidate *candidate = NULL, **to_detach = NULL;
+ uint64_t scan_secs;
+ int i, j, n_detach, n_discard;
+
+ if (!S_ISREG(attrs->loa_mode)) {
+ /* skip non-regular file */
+ return 0;
+ }
+
+ path = strdup(object->u.lo_posix.lop_path);
+ if (path == NULL) {
+ rc = -errno;
+ goto out;
+ }
+
+ const char * name = basename(path);
+ rc = sscanf(name, SFID, RFID(&fid));
+ if (rc != 3) {
+ /* Not an valid fid string, skip it */
+ rc = 0;
+ goto out;
+ }
+
+ candidate = lpcc_purge_candidate_new(attrs->loa_atime_ms, fid, object->u.lo_posix.lop_path);
+ if (candidate == NULL) {
+ rc = -ENOMEM;
+ goto out;
+ }
+
+ /* Increase statistic data */
+ pthread_mutex_lock(&stats.s_lock);
+ stats.s_scanned_objs++;
+ pthread_mutex_unlock(&stats.s_lock);
+
+ llapi_printf(LLAPI_MSG_DEBUG, "append fid: "DFID"\n", PFID(&fid));
+ pthread_mutex_lock(&candidate_set.cs_lock);
+ lpcc_purge_candidate_set_append(candidate);
+ candidate = NULL;
+
+ /*
+ * check whether the pool is full or has scanned continously
+ * for too long time
+ */
+ scan_secs = time(NULL) - stats.s_scan_start_sec;
+ if (candidate_set.cs_count < candidate_set.cs_capacity &&
+ scan_secs < opt.o_max_scan_secs) {
+ pthread_mutex_unlock(&candidate_set.cs_lock);
+ rc = 0;
+ goto out;
+ }
+
+ /* pool is full or scan for too long time */
+ llapi_printf(LLAPI_MSG_DEBUG, "start purging, candidate number: %u, "
+ "scan secs: %lu\n", candidate_set.cs_count, scan_secs);
+ lpcc_purge_candidate_set_sort();
+
+ /* (n_detach + n_discard) = 10% * candidate_num if start_ussage is 100 */
+ n_detach = (stats.s_start_usage - opt.o_low_usage) / 1000 *
+ candidate_set.cs_capacity;
+ n_discard = opt.o_low_usage / 1000 * candidate_set.cs_count;
+
+ if (n_detach <= 0) {
+ n_detach = 0;
+ } else {
+ to_detach = calloc(n_detach, sizeof(to_detach[0]));
+ if (to_detach == NULL) {
+ llapi_error(LLAPI_MSG_FATAL, errno,
+ "cannot allocate memory");
+ exit(1);
+ }
+ }
+
+ llapi_printf(LLAPI_MSG_DEBUG,
+ "copy out %d elements from the head\n", n_detach);
+ for (i = 0; i < n_detach; i++) {
+ to_detach[i] = candidate_set.cs_arr[i];
+ }
+
+ llapi_printf(LLAPI_MSG_DEBUG,
+ "shift pool and remove %d elements from the tail\n", n_discard);
+ memmove(&candidate_set.cs_arr[0], &candidate_set.cs_arr[n_detach],
+ (candidate_set.cs_count - n_detach - n_discard) * sizeof(candidate_set.cs_arr[0]));
+
+ for (j = candidate_set.cs_count - n_discard; j < candidate_set.cs_count; j++) {
+ lpcc_purge_candidate_destroy(candidate_set.cs_arr[j]);
+ }
+ candidate_set.cs_count = candidate_set.cs_count - n_detach - n_discard;
+
+ stats.s_scan_start_sec = time(NULL);
+ pthread_mutex_unlock(&candidate_set.cs_lock);
+
+ /* Detach files from candidates */
+ llapi_printf(LLAPI_MSG_DEBUG, "detach files...\n");
+ for (i = 0; i < n_detach; i++) {
+ rc = lpcc_purge_detach_candidate(opt.o_mount, to_detach[i]);
+ lpcc_purge_candidate_destroy(to_detach[i]);
+ }
+
+ rc = 0;
+out:
+ free(path);
+ lpcc_purge_candidate_destroy(candidate);
+ free(to_detach);
+
+ return rc;
+}
+
+static void lpcc_purge_scan(void)
+{
+ int rc;
+ struct lipe_policy policy;
+ struct scan_result result;
+ bool ldd_err;
+
+ llapi_printf(LLAPI_MSG_INFO, "do scanning...\n");
+
+ stats.s_scan_times++;
+ stats.s_start_usage = lpcc_purge_get_fs_usage(opt.o_cache);
+ stats.s_scanned_objs = 0;
+ stats.s_purged_objs = 0;
+ stats.s_scan_start_sec = time(NULL);
+
+ lipe_policy_init(&policy);
+ policy.lp_attr_bits = LIPE_OBJECT_ATTR_ATTR;
+ memset(&result, 0x00, sizeof(result));
+
+ llapi_printf(LLAPI_MSG_DEBUG, "start scanning...\n");
+
++ rc = lipe_scan(instance,
++ &policy,
++ &result,
++ NULL /* sum_counter_list */,
++ NULL /* sum_classify_list */,
++ opt.o_scan_threads,
++ "lpcc_purge" /* workspace */,
++ true /* abort_failure */,
++ &ldd_err);
+ if (rc < 0) {
+ llapi_error(LLAPI_MSG_ERROR, rc,
+ "failed to run lipe_scan");
+ }
+
+ llapi_printf(LLAPI_MSG_INFO, "scanning end\n");
+}
+
+static void lpcc_purge_free_space(void)
+{
+ int i;
+
+ llapi_printf(LLAPI_MSG_INFO, "free space...\n");
+
+ lpcc_purge_candidate_set_sort();
+
+ for (i = 0; i < candidate_set.cs_count; i++) {
+ double usage = lpcc_purge_get_fs_usage(opt.o_cache);
+ if (usage >= opt.o_low_usage || opt.o_dry_run)
+ lpcc_purge_detach_candidate(opt.o_mount, candidate_set.cs_arr[i]);
+ else
+ break;
+ }
+
+ fflush(stdout);
+ lpcc_purge_candidate_set_clear();
+}
+
+int main(int argc, char *argv[])
+{
+ int rc = 0;
+ llapi_msg_set_level(LLAPI_MSG_INFO);
+
+ signal(SIGUSR1, &lpcc_purge_usr1_handler);
+ signal(SIGUSR2, &lpcc_purge_null_handler);
+
+ signal(SIGINT, &lpcc_purge_sigint_handler);
+ signal(SIGTERM, &lpcc_purge_sigint_handler);
+
+ lpcc_purge_parse_opts(argc, argv);
+ lpcc_purge_verify_opts();
+ lpcc_purge_lock_pidfile();
+
+ lpcc_purge_candidate_set_init(opt.o_candidate_num);
+
+ instance = lipe_instance_alloc(0, opt.o_cache);
+ instance->li_callback = lpcc_purge_scan_callback;
+
+ while(1) {
+ lpcc_purge_wait_for_scan();
+
+ stats.s_end_time = 0;
+ stats.s_start_time = time(NULL);
+
+ lpcc_purge_scan();
+
+ lpcc_purge_free_space();
+
+ stats.s_end_time = time(NULL);
+
+ if (opt.o_dry_run) {
+ break;
+ }
+ }
+
+ lipe_instance_free(instance);
+ lpcc_purge_candidate_set_destroy();
+
+ return rc == 0 ? 0 : EXIT_FAILURE;
+}