/*
 *
 *  vpd.c - PCI VPD (Vital Product Data) parser
 *
 * Author: Michael S. Tsirkin <mst@mellanox.co.il>
 *
 * Copyright (c) 2013-2021 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
 *
 * This software is available to you under a choice of one of two
 * licenses.  You may choose to be licensed under the terms of the GNU
 * General Public License (GPL) Version 2, available from the file
 * COPYING in the main directory of this source tree, or the
 * OpenIB.org BSD license below:
 *
 *     Redistribution and use in source and binary forms, with or
 *     without modification, are permitted provided that the following
 *     conditions are met:
 *
 *      - Redistributions of source code must retain the above
 *        copyright notice, this list of conditions and the following
 *        disclaimer.
 *
 *      - Redistributions in binary form must reproduce the above
 *        copyright notice, this list of conditions and the following
 *        disclaimer in the documentation and/or other materials
 *        provided with the distribution.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/times.h>
#include <unistd.h>
#include <ctype.h>

#include "tools_version.h"
#include "mvpd/mvpd.h"

static clock_t timeout_t = 5; // Just for compatibility, no meaning

#define VPD_MAX_SIZE 4096
#define VPD_TOOL_VERSON "2.0.0"

#define OPTSTR "mvhnsrt:"

typedef unsigned char vpd_t[VPD_MAX_SIZE];

void print_field(char *key, char *val)
{
    if (strcmp(key, "RV") == 0) {
        return;
    } else if (strlen(val)) {
        printf("%s:      %s\n", key, val);
    }
}

void print_bin_field(char *key, char *val)
{
    char *valTmp = malloc((strlen(val) * 2 + 1) * sizeof(char));
    if (!valTmp) {
        printf("-E- Failed to Allocate Memory");
        return;
    }
    memset(valTmp, 0, (strlen(val) * 2 + 1) * sizeof(char));
    int i = 0;
    while (val[i] != '\0') {
        sprintf(valTmp + i * 2, "%02x", (u_int8_t) val[i]);
        i++;
    }
    printf("%s(BIN): %s\n", key, valTmp);
    free(valTmp);
}

int is_printable(const char *value)
{
    int i = 0;
    while (value[i] != '\0') {
        if (isprint((int)value[i])) {
            i++;
            continue;
        } else {
            return 0;
        }
    }
    return 1;
}

static int is_keyword_valid(const char* kw)
{
    if (!kw || strlen(kw) == 0 || !is_printable(kw)) {
        return 0;
    }
    return 1;
}

//If you want to print all, pass NULL keyword
//TODO adrianc: use common func - there is alot of duplicate code here.
void find_and_print_vpd_data(vpd_result_t *vpd_data, vpd_tags_type_t vpd_type, const char *keyword)
{
    if (vpd_type & VPD_RO) {
        int i;
        if (!keyword || strcmp(keyword, "ID") == 0) {
            print_field("ID", vpd_data->id.data);
            if (keyword) {
                return;
            }
        }
        for (i = 0; i < vpd_data->ro_fields_size; i++) {
            if (!keyword || strcmp(vpd_data->ro_fields[i].id, keyword) == 0) {
                if (is_keyword_valid(vpd_data->ro_fields[i].id)) {
                    // skip empty/non-printable keywords
                    print_field(vpd_data->ro_fields[i].id, vpd_data->ro_fields[i].data);
                }
                if (keyword) {
                    return;
                }
            }
        }
    }
    if (vpd_type & VPD_RW) {
        int i;
        for (i = 0; i < vpd_data->rw_fields_size; i++) {
            if (!keyword || strcmp(vpd_data->rw_fields[i].id, keyword) == 0) {
                if (is_keyword_valid(vpd_data->rw_fields[i].id)) {
                    // skip empty/non-printable keywords
                    if (is_printable(vpd_data->rw_fields[i].data) == 1) {
                        print_field(vpd_data->rw_fields[i].id, vpd_data->rw_fields[i].data);
                    } else {
                        print_bin_field(vpd_data->rw_fields[i].id, vpd_data->rw_fields[i].data);
                    }
                }
                if (keyword) {
                    return;
                }
            }
        }
    }
}

//Returns a pointer to the position of the arg in OPTSTR, or NULL if not found
char const* get_opt_pos(const char *option_suspect, int enforce_option_with_args)
{
    if (!(strlen(option_suspect) == 2 && option_suspect[0] == '-')) {
        return NULL;
    }
    char suspect_to_search[3];
    suspect_to_search[0] = option_suspect[1];
    if (enforce_option_with_args) {
        suspect_to_search[1] = ':';
        suspect_to_search[2] = 0;
    } else {
        suspect_to_search[1] = 0;
    }
    return strstr(OPTSTR, suspect_to_search);
}

int verify_command_layout(int argc, char const **argv)
{
    if (argc < 2) {
        return 0;
    }

    int i;
    for (i = 1; i < argc; i++) {
        if (get_opt_pos(argv[i], 0) || get_opt_pos(argv[i - 1], 1)) {
            continue;
        } else {
            break;
        }
    }
    //i is file argument or at the end of argv
    for (i = i + 1; i < argc; i++) {
        if (get_opt_pos(argv[i], 0)) {
            return 0;
        }
    }
    return 1;
}

int main(int argc, char **argv)
{
    const char *name;
    char *endptr;
    int i;
    int rc = 0;
    vpd_t d;
    int m = 0;
    int n = 0;
    int strict = 0;
    mfile *mf;
    vpd_result_t *read_result;
    vpd_tags_type_t read_type = VPD_ALL;

    if (!verify_command_layout(argc, (char const**) argv)) {
        fprintf(stderr, "-E- Bad input parameter.\n");
        rc = 1;
        goto usage;
    }

    do {
        i = getopt(argc, argv, OPTSTR);
        if (i < 0) {
            break;
        }

        switch (i) {
        case 'm':
            m = 1;
            break;

        case 'n':
            n = 1;
            break;

        case 's':
            strict = 1;
            break;

        case 'h':
            rc = 0;
            goto usage;

        case 'v':
            print_version_string("mstvpd", VPD_TOOL_VERSON);
            exit(0);

        case 'r':
            read_type = VPD_RO;
            break;

        case 't':
            timeout_t = strtol(optarg, &endptr, 0);
            if (*endptr != '\0') {
                fprintf(stderr, "-E- Invalid timeout argument: %s.\n", optarg);
                return 1;
            }
            if (timeout_t <= 0) {
                fprintf(stderr, "-E- Wrong timeout, it should be > 0 !\n");
                return 1;
            }
            break;

        default:
            rc = 1;
            goto usage;
        }
    } while (1 == 1);

    name = argv[optind];
    argc -= optind;
    argv += optind;
    if (name == NULL) {
        fprintf(stderr, "-E- Missing <file> argument !\n");
        return 33;
    }
    if (!strcmp("-", name)) {
        if (fread(d, VPD_MAX_SIZE, 1, stdin) != 1) {
            return 3;
        }
    } else {
        mf = mopen(name);
        if (!mf) {
            fprintf(stderr, "-E- Failed to open device %s!\n", name);
            return MVPD_BAD_PARAMS;
        }
        int mvpd_len = VPD_MAX_SIZE;
        rc = mvpd_get_vpd_size(mf, &mvpd_len);
        if (rc != 0) {
            fprintf(stderr, "-E- Failed to get VPD size from %s!\n", name);
            return MVPD_ERR;
        }
        rc = mvpd_get_raw_vpd(mf, (u_int8_t*) d, mvpd_len);
        if (rc) {
            fprintf(stderr, "-E- Failed to read VPD from %s!\n", name);
            return MVPD_ERR;
        }
    }

    if (m) {
        return fwrite(d, VPD_MAX_SIZE, 1, stdout) != 1;
    }
    rc = mvpd_parse_adv((u_int8_t*) d, VPD_MAX_SIZE, &read_result, read_type, strict, !n);
    if (rc) {
        fprintf(stderr, "-E- Failed to parse VPD from %s!\n", name);
        return MVPD_ERR;
    }
    if (argc == 1) {
        find_and_print_vpd_data(read_result, read_type, NULL);
    } else {
        for (i = 0; i < argc - 1; ++i) {
            if (!strcmp(argv[i + 1], "--")) {
                continue;
            }
            find_and_print_vpd_data(read_result, read_type, argv[i + 1]);
        }
    }
    if (read_result) {
        mvpd_result_free(read_result);
    }
    return 0;

usage: printf("Usage: %s [-m|-n|-r] [-t ##] <file> [-- keyword ...]\n", argv[0]);
    printf("-h\tPrint this help.\n");
    printf("-v\tPrint tool version.\n");
    printf("-m\tDump raw VPD data to stdout.\n");
    printf("-n\tDo not validate check sum.\n");
    printf("-r\tDo not check and display the VPD_W tag in the vpd data.\n");
    printf("-t ##\tTime out after ## seconds. (Default is 30.)\n\n");  // Currently ignored - for compatibility
    // We have also "-s" ("s" for "strict"), which is a hidden flag used by production.
    // There are cards out there with inconsistent format, ~200K cards,
    // which will cause the tool to fail if this flag is specified.
    printf("file\tThe PCI id number of the HCA (for example, \"2:00.0\"),\n");
    printf("\tthe device name (such as \"mlx4_0\")\n");
    printf("\tthe absolute path to the device (\"/sys/class/infiniband/mlx4_0/device\")\n");
    printf("\tor '-' to read VPD data from the standard input.\n\n");
    printf("keyword(s): Only display the requested information. (ID, PN, EC, SN, etc...)\n");
    return rc;
}
