/* SPDX-License-Identifier: LGPL-2.1-or-later */

#include <unistd.h>

#include "alloc-util.h"
#include "analyze.h"
#include "analyze-pcrs.h"
#include "ansi-color.h"
#include "fileio.h"
#include "format-table.h"
#include "hexdecoct.h"
#include "log.h"
#include "strv.h"
#include "tpm2-util.h"

static int get_pcr_alg(const char **ret) {
        assert(ret);

        FOREACH_STRING(alg, "sha256", "sha384", "sha1") {
                _cleanup_free_ char *p = NULL;

                if (asprintf(&p, "/sys/class/tpm/tpm0/pcr-%s/0", alg) < 0)
                        return log_oom();

                if (access(p, F_OK) < 0) {
                        if (errno != ENOENT)
                                return log_error_errno(errno, "Failed to determine whether %s exists: %m", p);
                } else {
                        *ret = alg;
                        return 1;
                }
        }

        log_notice("Kernel does not support reading PCR values.");
        *ret = NULL;
        return 0;
}

static int get_current_pcr(const char *alg, uint32_t pcr, void **ret, size_t *ret_size) {
        _cleanup_free_ char *p = NULL, *s = NULL;
        _cleanup_free_ void *buf = NULL;
        size_t ss = 0, bufsize = 0;
        int r;

        assert(alg);
        assert(ret);
        assert(ret_size);

        if (asprintf(&p, "/sys/class/tpm/tpm0/pcr-%s/%" PRIu32, alg, pcr) < 0)
                return log_oom();

        r = read_virtual_file(p, 4096, &s, &ss);
        if (r < 0)
                return log_error_errno(r, "Failed to read '%s': %m", p);

        r = unhexmem_full(s, ss, /* secure = */ false, &buf, &bufsize);
        if (r < 0)
                return log_error_errno(r, "Failed to decode hex PCR data '%s': %m", s);

        *ret = TAKE_PTR(buf);
        *ret_size = bufsize;
        return 0;
}

static int add_pcr_to_table(Table *table, const char *alg, uint32_t pcr) {
        _cleanup_free_ char *h = NULL;
        const char *color = NULL;
        int r;

        if (alg) {
                _cleanup_free_ void *buf = NULL;
                size_t bufsize = 0;

                r = get_current_pcr(alg, pcr, &buf, &bufsize);
                if (r < 0)
                        return r;

                h = hexmem(buf, bufsize);
                if (!h)
                        return log_oom();

                /* Grey out PCRs that are not sensibly initialized */
                if (memeqbyte(0, buf, bufsize) ||
                    memeqbyte(0xFFU, buf, bufsize))
                        color = ansi_grey();
        }

        r = table_add_many(table,
                           TABLE_UINT32, pcr,
                           TABLE_STRING, tpm2_pcr_index_to_string(pcr),
                           TABLE_STRING, h,
                           TABLE_SET_COLOR, color);
        if (r < 0)
                return table_log_add_error(r);

        return 0;
}

int verb_pcrs(int argc, char *argv[], void *userdata) {
        _cleanup_(table_unrefp) Table *table = NULL;
        const char *alg = NULL;
        int r;

        if (!tpm2_is_fully_supported())
                log_notice("System lacks full TPM2 support, not showing PCR state.");
        else {
                r = get_pcr_alg(&alg);
                if (r < 0)
                        return r;
        }

        table = table_new("nr", "name", alg ?: "-");
        if (!table)
                return log_oom();

        (void) table_set_align_percent(table, table_get_cell(table, 0, 0), 100);
        (void) table_set_ersatz_string(table, TABLE_ERSATZ_DASH);

        if (!alg) /* hide hash column if we couldn't acquire it */
                (void) table_set_display(table, 0, 1);

        char **args = strv_skip(argv, 1);
        if (args) {
                STRV_FOREACH(arg, args) {
                        int pi;

                        pi = tpm2_pcr_index_from_string(*arg);
                        if (pi < 0)
                                return log_error_errno(pi, "PCR index \"%s\" not known.", *arg);

                        r = add_pcr_to_table(table, alg, pi);
                        if (r < 0)
                                return r;
                }

                (void) table_set_sort(table, (size_t) 0);
        } else
                for (uint32_t pi = 0; pi < _TPM2_PCR_INDEX_MAX_DEFINED; pi++) {
                        r = add_pcr_to_table(table, alg, pi);
                        if (r < 0)
                                return r;
                }

        r = table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, /* show_header= */true);
        if (r < 0)
                return r;

        return EXIT_SUCCESS;
}
