func ToCommandLine()

in rule/rule.go [106:319]


func ToCommandLine(wf WireFormat, resolveIds bool) (rule string, err error) {
	ar, err := fromWireFormat(wf)
	if err != nil {
		return "", fmt.Errorf("failed to parse wire format: %w", err)
	}

	r := ruleData{}
	if err = r.fromAuditRuleData(ar); err != nil {
		return "", fmt.Errorf("failed to parse audit rule: %w", err)
	}

	list, err := r.getList()
	if err != nil {
		return "", err
	}

	act, err := r.getAction()
	if err != nil {
		return "", err
	}

	existingFields := make(map[field]int)
	for idx, fieldID := range r.fields {
		existingFields[fieldID] = idx
	}

	// Detect if rule is a watch.
	// Must have all syscalls and perm field. Only other valid fields are
	// dir, path and key, according to auditctl source
	if permIdx, ok := existingFields[permField]; r.allSyscalls && ok {
		extraFields, pos := false, 0
		var path, key string
	loop:
		for _, fieldID := range r.fields {
			switch fieldID {
			case keyField, pathField, dirField:
				if pos >= len(r.strings) {
					return "", fmt.Errorf("no buffer data for path field %d", fieldID)
				}
				if fieldID == keyField {
					key = r.strings[pos]
				} else {
					path = r.strings[pos]
				}
				pos++
			case permField:
			default:
				extraFields = true
				break loop
			}
		}
		if !extraFields {
			arguments := []string{"-w", path, "-p", permission(r.values[permIdx]).String()}
			if len(key) > 0 {
				arguments = append(arguments, "-k", key)
			}
			return strings.Join(arguments, " "), nil
		}
	}

	// Parse rule as syscall type

	arguments := []string{
		"-a",
		fmt.Sprintf("%s,%s", act, list),
	}

	// Parse arch field first, if present
	// Here there is a significant difference to what auditctl does.
	// Auditctl will allow to install a rule for a different platform
	// (i.e. "aarch64" when the actual platform is "x86_64"). A rule like this
	// will never trigger any events in the kernel.
	// When such a rule is printed with `auditctl -l`, it will show as
	// "-F arch=b64", which is wrong.
	// This code will print the real value, "aarch64".
	if fieldIdx, found := existingFields[archField]; found {
		r.arch, err = getDisplayArch(r.values[fieldIdx])
		if err != nil {
			return "", err
		}
		arguments = append(arguments, "-F", fmt.Sprintf("arch=%s", r.arch))
	}

	// Parse syscalls
	if r.allSyscalls {
		if r.flags == exitFilter || r.flags == entryFilter {
			arguments = append(arguments, "-S", "all")
		}
	} else if len(r.syscalls) > 0 {
		arch, err := getRuntimeArch()
		if err != nil {
			return "", err
		}
		if r.arch == "b32" {
			switch arch {
			case "i386", "arm", "ppc", "s390":
			case "aarch64":
				arch = "arm"
			case "x86_64":
				arch = "i386"
			case "ppc64", "ppc64le":
				arch = "ppc"
			case "s390x":
				arch = "s390"
			default:
				return "", fmt.Errorf("invalid arch for b32: '%s'", arch)
			}
		} else if len(r.arch) > 0 && r.arch != "b64" {
			arch = r.arch
		}
		syscallTable, ok := auparse.AuditSyscalls[arch]
		if !ok {
			return "", fmt.Errorf("no syscall table for arch %s", arch)
		}
		list := make([]string, len(r.syscalls))
		for idx, syscallID := range r.syscalls {
			list[idx], ok = syscallTable[int(syscallID)]
			if !ok {
				return "", fmt.Errorf("syscall %d not found for arch %s", syscallID, arch)
			}
		}

		arguments = append(arguments, "-S", strings.Join(list, ","))
	}

	// Parse fields
	stringIndex := 0
	for idx, fieldID := range r.fields {
		op, found := reverseOperatorsTable[r.fieldFlags[idx]]
		if !found {
			return "", fmt.Errorf("field operator %x not found", r.fieldFlags[idx])
		}
		switch fieldID {
		case archField:
			// arch already handled
		case fieldCompare:
			fieldIds, found := reverseComparisonsTable[comparison(r.values[idx])]
			if !found {
				return "", errors.New("comparison code not valid")
			}
			if fieldIds[1] < fieldIds[0] {
				fieldIds[0], fieldIds[1] = fieldIds[1], fieldIds[0]
			}
			var fields [2]string
			for idx, id := range fieldIds {
				if fields[idx], found = reverseFieldsTable[id]; !found {
					return "", fmt.Errorf("unknown field %d", id)
				}
			}
			arguments = append(arguments, fmt.Sprintf("-C %s%s%s",
				fields[0], op, fields[1]))

		default:
			lhs, found := reverseFieldsTable[fieldID]
			if !found {
				return "", fmt.Errorf("field %x not found", fieldID)
			}
			value := r.values[idx]
			var rhs string
			switch fieldID {
			// Fields that take a string
			case objectUserField, objectRoleField, objectTypeField, objectLevelLowField,
				objectLevelHighField, pathField, dirField, subjectUserField,
				subjectRoleField, subjectTypeField, subjectSensitivityField,
				subjectClearanceField, keyField, exeField:
				if stringIndex >= len(r.strings) {
					return "", errors.New("string buffer overflow")
				}
				rhs = r.strings[stringIndex]
				stringIndex++
			case exitField:
				exitCode := int(int32(value))
				if errnoValue, ok := auparse.AuditErrnoToName[-exitCode]; ok {
					rhs = fmt.Sprintf("-%s", errnoValue)
				} else {
					rhs = strconv.Itoa(exitCode)
				}
			case uidField, euidField, suidField, fsuidField, auidField, objectUIDField:
				rhs = strconv.Itoa(int(int32(value)))
				if resolveIds {
					if user, err := user.LookupId(rhs); err == nil {
						rhs = user.Username
					}
				}
			case gidField, egidField, sgidField, fsgidField, objectGIDField:
				rhs = strconv.Itoa(int(int32(value)))
				if resolveIds {
					if group, err := user.LookupGroupId(rhs); err == nil {
						rhs = group.Name
					}
				}
			case msgTypeField:
				if value <= math.MaxUint16 {
					rhs = auparse.AuditMessageType(value).String()
				} else {
					rhs = fmt.Sprintf("UNKNOWN[%d]", value)
				}
			case permField:
				rhs = permission(value).String()
			case filetypeField:
				if resolveIds {
					rhs = filetype(value).String()
				} else {
					rhs = strconv.Itoa(int(value))
				}
			default:
				rhs = strconv.Itoa(int(value))
			}
			arguments = append(arguments, fmt.Sprintf("-F %s%s%s", lhs, op, rhs))
		}
	}

	return strings.Join(arguments, " "), nil
}