/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/

/***
  This file is part of systemd.

  Copyright 2013 Kay Sievers

  systemd is free software; you can redistribute it and/or modify it
  under the terms of the GNU Lesser General Public License as published by
  the Free Software Foundation; either version 2.1 of the License, or
  (at your option) any later version.

  systemd is distributed in the hope that it will be useful, but
  WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public License
  along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/

#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <getopt.h>
#include <locale.h>
#include <string.h>
#include <fnmatch.h>
#include <fcntl.h>
#include <sys/timex.h>

#include "boot.h"
#include "boot-loader.h"
#include "build.h"
#include "util.h"
#include "strv.h"
#include "efivars.h"
#include "conf-files.h"

static char *tilt_slashes(char *s) {
        char *p;

        if (!s)
                return NULL;

        for (p = s; *p; p++)
                if (*p == '\\')
                        *p = '/';
        return s;
}

static int get_boot_entries(struct boot_info *info) {
        uint16_t *list;
        int i, n;
        int err = 0;

        n = efi_get_boot_options(&list);
        if (n < 0)
                return n;

        for (i = 0; i < n; i++) {
                struct boot_info_entry *e;

                e = realloc(info->fw_entries, (info->fw_entries_count+1) * sizeof(struct boot_info_entry));
                if (!e) {
                        err = -ENOMEM;
                                break;
                }
                info->fw_entries = e;

                e = &info->fw_entries[info->fw_entries_count];
                memset(e, 0, sizeof(struct boot_info_entry));
                e->order = -1;

                err = efi_get_boot_option(list[i], &e->title, &e->part_uuid, &e->path);
                if (err < 0)
                        continue;

                if (isempty(e->title)) {
                        free(e->title);
                        e->title = NULL;
                }
                tilt_slashes(e->path);

                e->id = list[i];
                info->fw_entries_count++;
        }

        free(list);
        return err;
}

static int find_active_entry(struct boot_info *info) {
        uint16_t boot_cur;
        void *buf;
        size_t l;
        size_t i;
        int err = -ENOENT;

        err = efi_get_variable(EFI_VENDOR_GLOBAL, "BootCurrent", NULL, &buf, &l);
        if (err < 0)
                return err;

        memcpy(&boot_cur, buf, sizeof(uint16_t));
        for (i = 0; i < info->fw_entries_count; i++) {
                if (info->fw_entries[i].id != boot_cur)
                        continue;
                info->fw_entry_active = i;
                err = 0;
                break;
        }
        free(buf);
        return err;
}

static int get_boot_order(struct boot_info *info) {
        size_t i, k;
        int r;

        r = efi_get_boot_order(&info->fw_entries_order);
        if (r < 0)
                return r;

        info->fw_entries_order_count = r;

        for (i = 0; i < info->fw_entries_order_count; i++) {
                for (k = 0; k < info->fw_entries_count; k++) {
                        if (info->fw_entries[k].id != info->fw_entries_order[i])
                                continue;
                        info->fw_entries[k].order = i;
                        break;
                }
        }

        return 0;
}

static int entry_cmp(const void *a, const void *b) {
        const struct boot_info_entry *e1 = a;
        const struct boot_info_entry *e2 = b;

        /* boot order of active entries */
        if (e1->order > 0 && e2->order > 0)
                return e1->order - e2->order;

        /* sort active entries before inactive ones */
        if (e1->order > 0)
                return 1;
        if (e2->order > 0)
                return -1;

        /* order of inactive entries */
        return e1->id - e2->id;
}

int boot_info_query(struct boot_info *info) {
        char str[64];
        char buf[64];
        char *loader_active = NULL;

        info->fw_secure_boot = is_efi_secure_boot();
        info->fw_secure_boot_setup_mode = is_efi_secure_boot_setup_mode();

        efi_get_variable_string(EFI_VENDOR_LOADER, "LoaderInfo", &info->loader);

        get_boot_entries(info);
        if (info->fw_entries_count > 0) {
                get_boot_order(info);
                qsort(info->fw_entries, info->fw_entries_count, sizeof(struct boot_info_entry), entry_cmp);
                find_active_entry(info);
        }

        efi_get_variable_string(EFI_VENDOR_LOADER, "LoaderFirmwareType", &info->fw_type);
        efi_get_variable_string(EFI_VENDOR_LOADER, "LoaderFirmwareInfo", &info->fw_info);
        efi_get_variable_string(EFI_VENDOR_LOADER, "LoaderImageIdentifier", &info->loader_image_path);
        tilt_slashes(info->loader_image_path);
        efi_get_loader_device_part_uuid(&info->loader_part_uuid);

        boot_loader_read_entries(info);
        efi_get_variable_string(EFI_VENDOR_LOADER, "LoaderEntrySelected", &loader_active);
        if (loader_active) {
                boot_loader_find_active_entry(info, loader_active);
                free(loader_active);
        }

        snprintf(str, sizeof(str), "LoaderEntryOptions-%s", sd_id128_to_string(info->machine_id, buf));
        efi_get_variable_string(EFI_VENDOR_LOADER, str, &info->loader_options_added);

        return 0;
}
