/* SPDX-License-Identifier: LGPL-2.1+ */

#include "bus-print-properties.h"
#include "cap-list.h"
#include "cgroup-util.h"
#include "escape.h"
#include "mountpoint-util.h"
#include "nsflags.h"
#include "parse-util.h"
#include "stdio-util.h"
#include "string-util.h"
#include "strv.h"
#include "time-util.h"
#include "user-util.h"

int bus_print_property_value(const char *name, const char *expected_value, bool only_value, const char *value) {
        assert(name);

        if (expected_value && !streq_ptr(expected_value, value))
                return 0;

        if (only_value)
                puts(value);
        else
                printf("%s=%s\n", name, value);

        return 0;
}

int bus_print_property_valuef(const char *name, const char *expected_value, bool only_value, const char *fmt, ...) {
        va_list ap;
        int r;

        assert(name);
        assert(fmt);

        if (expected_value) {
                _cleanup_free_ char *s = NULL;

                va_start(ap, fmt);
                r = vasprintf(&s, fmt, ap);
                va_end(ap);
                if (r < 0)
                        return -ENOMEM;

                if (streq_ptr(expected_value, s)) {
                        if (only_value)
                                puts(s);
                        else
                                printf("%s=%s\n", name, s);
                }

                return 0;
        }

        if (!only_value)
                printf("%s=", name);
        va_start(ap, fmt);
        vprintf(fmt, ap);
        va_end(ap);
        puts("");

        return 0;
}

static int bus_print_property(const char *name, const char *expected_value, sd_bus_message *m, bool value, bool all) {
        char type;
        const char *contents;
        int r;

        assert(name);
        assert(m);

        r = sd_bus_message_peek_type(m, &type, &contents);
        if (r < 0)
                return r;

        switch (type) {

        case SD_BUS_TYPE_STRING: {
                const char *s;

                r = sd_bus_message_read_basic(m, type, &s);
                if (r < 0)
                        return r;

                if (all || !isempty(s)) {
                        bool good;

                        /* This property has a single value, so we need to take
                         * care not to print a new line, everything else is OK. */
                        good = !strchr(s, '\n');
                        bus_print_property_value(name, expected_value, value, good ? s : "[unprintable]");
                }

                return 1;
        }

        case SD_BUS_TYPE_BOOLEAN: {
                int b;

                r = sd_bus_message_read_basic(m, type, &b);
                if (r < 0)
                        return r;

                if (expected_value && parse_boolean(expected_value) != b)
                        return 1;

                bus_print_property_value(name, NULL, value, yes_no(b));
                return 1;
        }

        case SD_BUS_TYPE_UINT64: {
                uint64_t u;

                r = sd_bus_message_read_basic(m, type, &u);
                if (r < 0)
                        return r;

                /* Yes, heuristics! But we can change this check
                 * should it turn out to not be sufficient */

                if (endswith(name, "Timestamp") ||
                    STR_IN_SET(name, "NextElapseUSecRealtime", "LastTriggerUSec", "TimeUSec", "RTCTimeUSec")) {
                        char timestamp[FORMAT_TIMESTAMP_MAX];
                        const char *t;

                        t = format_timestamp(timestamp, sizeof(timestamp), u);
                        if (t || all)
                                bus_print_property_value(name, expected_value, value, strempty(t));

                } else if (strstr(name, "USec")) {
                        char timespan[FORMAT_TIMESPAN_MAX];

                        (void) format_timespan(timespan, sizeof(timespan), u, 0);
                        bus_print_property_value(name, expected_value, value, timespan);

                } else if (streq(name, "CoredumpFilter")) {
                        char buf[STRLEN("0xFFFFFFFF")];

                        xsprintf(buf, "0x%"PRIx64, u);
                        bus_print_property_value(name, expected_value, value, buf);

                } else if (streq(name, "RestrictNamespaces")) {
                        _cleanup_free_ char *s = NULL;
                        const char *result;

                        if ((u & NAMESPACE_FLAGS_ALL) == 0)
                                result = "yes";
                        else if (FLAGS_SET(u, NAMESPACE_FLAGS_ALL))
                                result = "no";
                        else {
                                r = namespace_flags_to_string(u, &s);
                                if (r < 0)
                                        return r;

                                result = strempty(s);
                        }

                        bus_print_property_value(name, expected_value, value, result);

                } else if (streq(name, "MountFlags")) {
                        const char *result;

                        result = mount_propagation_flags_to_string(u);
                        if (!result)
                                return -EINVAL;

                        bus_print_property_value(name, expected_value, value, result);

                } else if (STR_IN_SET(name, "CapabilityBoundingSet", "AmbientCapabilities")) {
                        _cleanup_free_ char *s = NULL;

                        r = capability_set_to_string_alloc(u, &s);
                        if (r < 0)
                                return r;

                        bus_print_property_value(name, expected_value, value, s);

                } else if ((STR_IN_SET(name, "CPUWeight", "StartupCPUWeight", "IOWeight", "StartupIOWeight") && u == CGROUP_WEIGHT_INVALID) ||
                           (STR_IN_SET(name, "CPUShares", "StartupCPUShares") && u == CGROUP_CPU_SHARES_INVALID) ||
                           (STR_IN_SET(name, "BlockIOWeight", "StartupBlockIOWeight") && u == CGROUP_BLKIO_WEIGHT_INVALID) ||
                           (STR_IN_SET(name, "MemoryCurrent", "TasksCurrent") && u == (uint64_t) -1) ||
                           (endswith(name, "NSec") && u == (uint64_t) -1))

                        bus_print_property_value(name, expected_value, value, "[not set]");

                else if ((STR_IN_SET(name, "DefaultMemoryLow", "DefaultMemoryMin", "MemoryLow", "MemoryHigh", "MemoryMax", "MemorySwapMax", "MemoryLimit") && u == CGROUP_LIMIT_MAX) ||
                         (STR_IN_SET(name, "TasksMax", "DefaultTasksMax") && u == (uint64_t) -1) ||
                         (startswith(name, "Limit") && u == (uint64_t) -1) ||
                         (startswith(name, "DefaultLimit") && u == (uint64_t) -1))

                        bus_print_property_value(name, expected_value, value, "infinity");
                else if (STR_IN_SET(name, "IPIngressBytes", "IPIngressPackets", "IPEgressBytes", "IPEgressPackets") && u == (uint64_t) -1)
                        bus_print_property_value(name, expected_value, value, "[no data]");
                else
                        bus_print_property_valuef(name, expected_value, value, "%"PRIu64, u);

                return 1;
        }

        case SD_BUS_TYPE_INT64: {
                int64_t i;

                r = sd_bus_message_read_basic(m, type, &i);
                if (r < 0)
                        return r;

                bus_print_property_valuef(name, expected_value, value, "%"PRIi64, i);
                return 1;
        }

        case SD_BUS_TYPE_UINT32: {
                uint32_t u;

                r = sd_bus_message_read_basic(m, type, &u);
                if (r < 0)
                        return r;

                if (strstr(name, "UMask") || strstr(name, "Mode"))
                        bus_print_property_valuef(name, expected_value, value, "%04o", u);

                else if (streq(name, "UID")) {
                        if (u == UID_INVALID)
                                bus_print_property_value(name, expected_value, value, "[not set]");
                        else
                                bus_print_property_valuef(name, expected_value, value, "%"PRIu32, u);
                } else if (streq(name, "GID")) {
                        if (u == GID_INVALID)
                                bus_print_property_value(name, expected_value, value, "[not set]");
                        else
                                bus_print_property_valuef(name, expected_value, value, "%"PRIu32, u);
                } else
                        bus_print_property_valuef(name, expected_value, value, "%"PRIu32, u);

                return 1;
        }

        case SD_BUS_TYPE_INT32: {
                int32_t i;

                r = sd_bus_message_read_basic(m, type, &i);
                if (r < 0)
                        return r;

                bus_print_property_valuef(name, expected_value, value, "%"PRIi32, i);
                return 1;
        }

        case SD_BUS_TYPE_DOUBLE: {
                double d;

                r = sd_bus_message_read_basic(m, type, &d);
                if (r < 0)
                        return r;

                bus_print_property_valuef(name, expected_value, value, "%g", d);
                return 1;
        }

        case SD_BUS_TYPE_ARRAY:
                if (streq(contents, "s")) {
                        bool first = true;
                        const char *str;

                        r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, contents);
                        if (r < 0)
                                return r;

                        while ((r = sd_bus_message_read_basic(m, SD_BUS_TYPE_STRING, &str)) > 0) {
                                _cleanup_free_ char *e = NULL;

                                e = shell_maybe_quote(str, ESCAPE_BACKSLASH_ONELINE);
                                if (!e)
                                        return -ENOMEM;

                                if (first) {
                                        if (!value)
                                                printf("%s=", name);
                                        first = false;
                                } else
                                        fputs(" ", stdout);

                                fputs(e, stdout);
                        }
                        if (r < 0)
                                return r;

                        if (first && all && !value)
                                printf("%s=", name);
                        if (!first || all)
                                puts("");

                        r = sd_bus_message_exit_container(m);
                        if (r < 0)
                                return r;

                        return 1;

                } else if (streq(contents, "y")) {
                        const uint8_t *u;
                        size_t n;

                        r = sd_bus_message_read_array(m, SD_BUS_TYPE_BYTE, (const void**) &u, &n);
                        if (r < 0)
                                return r;

                        if (all || n > 0) {
                                unsigned i;

                                if (!value)
                                        printf("%s=", name);

                                for (i = 0; i < n; i++)
                                        printf("%02x", u[i]);

                                puts("");
                        }

                        return 1;

                } else if (streq(contents, "u")) {
                        uint32_t *u;
                        size_t n;

                        r = sd_bus_message_read_array(m, SD_BUS_TYPE_UINT32, (const void**) &u, &n);
                        if (r < 0)
                                return r;

                        if (all || n > 0) {
                                unsigned i;

                                if (!value)
                                        printf("%s=", name);

                                for (i = 0; i < n; i++)
                                        printf("%08x", u[i]);

                                puts("");
                        }

                        return 1;
                }

                break;
        }

        return 0;
}

int bus_message_print_all_properties(
                sd_bus_message *m,
                bus_message_print_t func,
                char **filter,
                bool value,
                bool all,
                Set **found_properties) {

        int r;

        assert(m);

        r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "{sv}");
        if (r < 0)
                return r;

        while ((r = sd_bus_message_enter_container(m, SD_BUS_TYPE_DICT_ENTRY, "sv")) > 0) {
                _cleanup_free_ char *name_with_equal = NULL;
                const char *name, *contents, *expected_value = NULL;

                r = sd_bus_message_read_basic(m, SD_BUS_TYPE_STRING, &name);
                if (r < 0)
                        return r;

                if (found_properties) {
                        r = set_ensure_put(found_properties, &string_hash_ops, name);
                        if (r < 0)
                                return log_oom();
                }

                name_with_equal = strjoin(name, "=");
                if (!name_with_equal)
                        return log_oom();

                if (!filter || strv_find(filter, name) ||
                    (expected_value = strv_find_startswith(filter, name_with_equal))) {
                        r = sd_bus_message_peek_type(m, NULL, &contents);
                        if (r < 0)
                                return r;

                        r = sd_bus_message_enter_container(m, SD_BUS_TYPE_VARIANT, contents);
                        if (r < 0)
                                return r;

                        if (func)
                                r = func(name, expected_value, m, value, all);
                        if (!func || r == 0)
                                r = bus_print_property(name, expected_value, m, value, all);
                        if (r < 0)
                                return r;
                        if (r == 0) {
                                if (all && !expected_value)
                                        printf("%s=[unprintable]\n", name);
                                /* skip what we didn't read */
                                r = sd_bus_message_skip(m, contents);
                                if (r < 0)
                                        return r;
                        }

                        r = sd_bus_message_exit_container(m);
                        if (r < 0)
                                return r;
                } else {
                        r = sd_bus_message_skip(m, "v");
                        if (r < 0)
                                return r;
                }

                r = sd_bus_message_exit_container(m);
                if (r < 0)
                        return r;
        }
        if (r < 0)
                return r;

        r = sd_bus_message_exit_container(m);
        if (r < 0)
                return r;

        return 0;
}

int bus_print_all_properties(
                sd_bus *bus,
                const char *dest,
                const char *path,
                bus_message_print_t func,
                char **filter,
                bool value,
                bool all,
                Set **found_properties) {

        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
        int r;

        assert(bus);
        assert(path);

        r = sd_bus_call_method(bus,
                        dest,
                        path,
                        "org.freedesktop.DBus.Properties",
                        "GetAll",
                        &error,
                        &reply,
                        "s", "");
        if (r < 0)
                return r;

        return bus_message_print_all_properties(reply, func, filter, value, all, found_properties);
}
