#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <stdint.h>
#include <ctype.h>
#include <err.h>
#include <endian.h>
#include <inttypes.h>
#include "common.h"
#include "output.h"
#define _STR(s) #s
#define STR(s) _STR(s)
#define TIME_STR_FMT "%a %b %d %Y %H:%M:%S"
#define UUID_STR_MAX 37
#define HEXDUMP_ROW_WIDTH 16
#define HEXDUMP_ROW_HEX_LEN (HEXDUMP_ROW_WIDTH * 3 + 1 + 1)
#define HEXDUMP_ROW_ASCII_LEN (HEXDUMP_ROW_WIDTH + 1)
#define SEPARATOR_CHAR '-'
#define MAX_INDENT 32
#define INDENT_CHAR ' '
static char out_indent_str[MAX_INDENT + 1];
static int out_indent_level;
static int out_vlevel;
static unsigned out_column_width = 20;
static FILE *out_fh;
static const char *out_prefix;
#define STR_MAX 256
int
outv_check(int vlevel)
{
return vlevel && (out_vlevel >= vlevel);
}
void
out_set_col_width(unsigned col_width)
{
out_column_width = col_width;
}
void
out_set_vlevel(int vlevel)
{
out_vlevel = vlevel;
if (out_fh == NULL)
out_fh = stdout;
}
void
out_set_prefix(const char *prefix)
{
out_prefix = prefix;
}
void
out_set_stream(FILE *stream)
{
out_fh = stream;
memset(out_indent_str, INDENT_CHAR, MAX_INDENT);
}
void
outv_err(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
outv_err_vargs(fmt, ap);
va_end(ap);
}
void
outv_err_vargs(const char *fmt, va_list ap)
{
char *_str = strdup(fmt);
if (!_str)
err(1, "strdup");
char *str = _str;
fprintf(stderr, "error: ");
int errstr = str[0] == '!';
if (errstr)
str++;
char *nl = strchr(str, '\n');
if (nl)
*nl = '\0';
vfprintf(stderr, str, ap);
if (errstr)
fprintf(stderr, ": %s", strerror(errno));
fprintf(stderr, "\n");
free(_str);
}
void
outv_indent(int vlevel, int i)
{
if (!outv_check(vlevel))
return;
out_indent_str[out_indent_level] = INDENT_CHAR;
out_indent_level += i;
if (out_indent_level < 0)
out_indent_level = 0;
if (out_indent_level > MAX_INDENT)
out_indent_level = MAX_INDENT;
out_indent_str[out_indent_level] = '\0';
}
static void
_out_prefix(void)
{
if (out_prefix)
fprintf(out_fh, "%s: ", out_prefix);
}
static void
_out_indent(void)
{
fprintf(out_fh, "%s", out_indent_str);
}
void
outv(int vlevel, const char *fmt, ...)
{
va_list ap;
if (!outv_check(vlevel))
return;
_out_prefix();
_out_indent();
va_start(ap, fmt);
vfprintf(out_fh, fmt, ap);
va_end(ap);
}
void
outv_nl(int vlevel)
{
if (!outv_check(vlevel))
return;
_out_prefix();
fprintf(out_fh, "\n");
}
void
outv_title(int vlevel, const char *fmt, ...)
{
va_list ap;
if (!outv_check(vlevel))
return;
fprintf(out_fh, "\n");
_out_prefix();
_out_indent();
va_start(ap, fmt);
vfprintf(out_fh, fmt, ap);
va_end(ap);
fprintf(out_fh, ":\n");
}
void
outv_field(int vlevel, const char *field, const char *fmt, ...)
{
va_list ap;
if (!outv_check(vlevel))
return;
_out_prefix();
_out_indent();
va_start(ap, fmt);
fprintf(out_fh, "%-*s : ", out_column_width, field);
vfprintf(out_fh, fmt, ap);
fprintf(out_fh, "\n");
va_end(ap);
}
const char *
out_get_percentage(double perc)
{
static char str_buff[STR_MAX] = {0, };
int ret = 0;
if (perc > 0.0 && perc < 0.0001) {
ret = snprintf(str_buff, STR_MAX, "%e %%", perc);
if (ret < 0)
return "";
} else {
int decimal = 0;
if (perc >= 100.0 || perc == 0.0)
decimal = 0;
else
decimal = 6;
ret = snprintf(str_buff, STR_MAX, "%.*f %%", decimal, perc);
if (ret < 0 || ret >= STR_MAX)
return "";
}
return str_buff;
}
const char *
out_get_size_str(uint64_t size, int human)
{
static char str_buff[STR_MAX] = {0, };
char units[] = {
'K', 'M', 'G', 'T', '\0'
};
const int nunits = sizeof(units) / sizeof(units[0]);
int ret = 0;
if (!human) {
ret = snprintf(str_buff, STR_MAX, "%"PRIu64, size);
} else {
int i = -1;
double dsize = (double)size;
uint64_t csize = size;
while (csize >= 1024 && i < nunits) {
csize /= 1024;
dsize /= 1024.0;
i++;
}
if (i >= 0 && i < nunits)
if (human == 1)
ret = snprintf(str_buff, STR_MAX, "%.1f%c",
dsize, units[i]);
else
ret = snprintf(str_buff, STR_MAX, "%.1f%c [%"
PRIu64"]", dsize, units[i], size);
else
ret = snprintf(str_buff, STR_MAX, "%"PRIu64,
size);
}
if (ret < 0 || ret >= STR_MAX)
return "";
return str_buff;
}
const char *
out_get_uuid_str(uuid_t uuid)
{
static char uuid_str[UUID_STR_MAX] = {0, };
int ret = util_uuid_to_string(uuid, uuid_str);
if (ret != 0) {
outv(2, "failed to covert uuid to string");
return NULL;
}
return uuid_str;
}
const char *
out_get_time_str(time_t time)
{
static char str_buff[STR_MAX] = {0, };
struct tm *tm = util_localtime(&time);
if (tm) {
strftime(str_buff, STR_MAX, TIME_STR_FMT, tm);
} else {
int ret = snprintf(str_buff, STR_MAX, "unknown");
if (ret < 0 || ret >= STR_MAX)
return "";
}
return str_buff;
}
static int
out_get_ascii_str(char *str, size_t str_len, const uint8_t *datap, size_t len)
{
int c = 0;
size_t i;
char pch;
if (str_len < len)
return -1;
for (i = 0; i < len; i++) {
pch = util_get_printable_ascii((char)datap[i]);
int t = snprintf(str + c, str_len - (size_t)c, "%c", pch);
if (t < 0)
return -1;
c += t;
}
return c;
}
static int
out_get_hex_str(char *str, size_t str_len, const uint8_t *datap, size_t len)
{
int c = 0;
size_t i;
int t;
if (str_len < (3 * len + 1))
return -1;
for (i = 0; i < len; i++) {
if (i && (i % 8) == 0) {
t = snprintf(str + c, str_len - (size_t)c, " ");
if (t < 0)
return -1;
c += t;
}
t = snprintf(str + c, str_len - (size_t)c, "%02x ", datap[i]);
if (t < 0)
return -1;
c += t;
}
return c;
}
void
outv_hexdump(int vlevel, const void *addr, size_t len, size_t offset, int sep)
{
if (!outv_check(vlevel) || len <= 0)
return;
const uint8_t *datap = (uint8_t *)addr;
uint8_t row_hex_str[HEXDUMP_ROW_HEX_LEN] = {0, };
uint8_t row_ascii_str[HEXDUMP_ROW_ASCII_LEN] = {0, };
size_t curr = 0;
size_t prev = 0;
int repeated = 0;
int n = 0;
while (len) {
size_t curr_len = min(len, HEXDUMP_ROW_WIDTH);
if (len != curr_len && curr &&
!memcmp(datap + prev, datap + curr, curr_len)) {
if (!repeated) {
fprintf(out_fh, "*\n");
repeated = 1;
}
} else {
repeated = 0;
int rh = out_get_hex_str((char *)row_hex_str,
HEXDUMP_ROW_HEX_LEN, datap + curr, curr_len);
int ra = out_get_ascii_str((char *)row_ascii_str,
HEXDUMP_ROW_ASCII_LEN, datap + curr, curr_len);
if (ra && rh)
n = fprintf(out_fh, "%08zx %-*s|%-*s|\n",
curr + offset,
HEXDUMP_ROW_HEX_LEN, row_hex_str,
HEXDUMP_ROW_WIDTH, row_ascii_str);
prev = curr;
}
len -= curr_len;
curr += curr_len;
}
if (sep && n) {
while (--n)
fprintf(out_fh, "%c", SEPARATOR_CHAR);
fprintf(out_fh, "\n");
}
}
const char *
out_get_checksum(void *addr, size_t len, uint64_t *csump)
{
static char str_buff[STR_MAX] = {0, };
int ret = 0;
void *buf = Malloc(len);
if (buf == NULL) {
ret = snprintf(str_buff, STR_MAX, "failed");
if (ret < 0 || ret >= STR_MAX)
return "";
return str_buff;
}
memcpy(buf, addr, len);
uint64_t *ncsump = (uint64_t *)
((char *)buf + ((char *)csump - (char *)addr));
uint64_t csum = *csump;
int valid = util_validate_checksum(buf, len, ncsump);
if (valid)
ret = snprintf(str_buff, STR_MAX, "0x%" PRIx64" [OK]",
le64toh(csum));
else
ret = snprintf(str_buff, STR_MAX,
"0x%" PRIx64 " [wrong! should be: 0x%" PRIx64 "]",
le64toh(csum), le64toh(*ncsump));
Free(buf);
if (ret < 0 || ret >= STR_MAX)
return "";
return str_buff;
}
const char *
out_get_btt_map_entry(uint32_t map)
{
static char str_buff[STR_MAX] = {0, };
int is_init = (map & ~BTT_MAP_ENTRY_LBA_MASK) == 0;
int is_zero = (map & ~BTT_MAP_ENTRY_LBA_MASK) ==
BTT_MAP_ENTRY_ZERO;
int is_error = (map & ~BTT_MAP_ENTRY_LBA_MASK) ==
BTT_MAP_ENTRY_ERROR;
int is_normal = (map & ~BTT_MAP_ENTRY_LBA_MASK) ==
BTT_MAP_ENTRY_NORMAL;
uint32_t lba = map & BTT_MAP_ENTRY_LBA_MASK;
int ret = snprintf(str_buff, STR_MAX, "0x%08x state: %s", lba,
is_init ? "init" :
is_zero ? "zero" :
is_error ? "error" :
is_normal ? "normal" : "unknown");
if (ret < 0 || ret >= STR_MAX)
return "";
return str_buff;
}
const char *
out_get_pool_type_str(pmem_pool_type_t type)
{
switch (type) {
case PMEM_POOL_TYPE_LOG:
return "log";
case PMEM_POOL_TYPE_BLK:
return "blk";
case PMEM_POOL_TYPE_OBJ:
return "obj";
case PMEM_POOL_TYPE_BTT:
return "btt";
case PMEM_POOL_TYPE_CTO:
return "cto";
default:
return "unknown";
}
}
const char *
out_get_pool_signature(pmem_pool_type_t type)
{
switch (type) {
case PMEM_POOL_TYPE_LOG:
return LOG_HDR_SIG;
case PMEM_POOL_TYPE_BLK:
return BLK_HDR_SIG;
case PMEM_POOL_TYPE_OBJ:
return OBJ_HDR_SIG;
case PMEM_POOL_TYPE_CTO:
return CTO_HDR_SIG;
default:
return NULL;
}
}
const char *
out_get_lane_section_str(enum lane_section_type type)
{
switch (type) {
case LANE_SECTION_ALLOCATOR:
return "allocator";
case LANE_SECTION_LIST:
return "list";
case LANE_SECTION_TRANSACTION:
return "tx";
default:
return "unknown";
}
}
const char *
out_get_tx_state_str(uint64_t state)
{
switch (state) {
case TX_STATE_NONE:
return "none";
case TX_STATE_COMMITTED:
return "committed";
default:
return "unknown";
}
}
const char *
out_get_chunk_type_str(enum chunk_type type)
{
switch (type) {
case CHUNK_TYPE_FOOTER:
return "footer";
case CHUNK_TYPE_FREE:
return "free";
case CHUNK_TYPE_USED:
return "used";
case CHUNK_TYPE_RUN:
return "run";
case CHUNK_TYPE_UNKNOWN:
default:
return "unknown";
}
}
const char *
out_get_chunk_flags(uint16_t flags)
{
if (flags & CHUNK_FLAG_COMPACT_HEADER)
return "compact header";
else if (flags & CHUNK_FLAG_HEADER_NONE)
return "header none";
return "";
}
const char *
out_get_zone_magic_str(uint32_t magic)
{
static char str_buff[STR_MAX] = {0, };
const char *correct = NULL;
switch (magic) {
case 0:
correct = "uninitialized";
break;
case ZONE_HEADER_MAGIC:
correct = "OK";
break;
default:
correct = "wrong! should be " STR(ZONE_HEADER_MAGIC);
break;
}
int ret = snprintf(str_buff, STR_MAX, "0x%08x [%s]", magic, correct);
if (ret < 0 || ret >= STR_MAX)
return "";
return str_buff;
}
const char *
out_get_pmemoid_str(PMEMoid oid, uint64_t uuid_lo)
{
static char str_buff[STR_MAX] = {0, };
int free_cor = 0;
int ret = 0;
char *correct = "OK";
if (oid.pool_uuid_lo && oid.pool_uuid_lo != uuid_lo) {
ret = snprintf(str_buff, STR_MAX,
"wrong! should be 0x%016"PRIx64, uuid_lo);
if (ret < 0 || ret >= STR_MAX)
err(1, "!snprintf");
correct = strdup(str_buff);
if (!correct)
err(1, "Cannot allocate memory for PMEMoid string\n");
free_cor = 1;
}
ret = snprintf(str_buff, STR_MAX,
"off: 0x%016"PRIx64" pool_uuid_lo: 0x%016"
PRIx64" [%s]", oid.off, oid.pool_uuid_lo, correct);
if (free_cor)
free(correct);
if (ret < 0 || ret >= STR_MAX)
err(1, "!snprintf");
return str_buff;
}
const char *
out_get_arch_machine_class_str(uint8_t machine_class)
{
switch (machine_class) {
case PMDK_MACHINE_CLASS_64:
return "64";
default:
return "unknown";
}
}
const char *
out_get_arch_data_str(uint8_t data)
{
switch (data) {
case PMDK_DATA_LE:
return "2's complement, little endian";
case PMDK_DATA_BE:
return "2's complement, big endian";
default:
return "unknown";
}
}
const char *
out_get_arch_machine_str(uint16_t machine)
{
static char str_buff[STR_MAX] = {0, };
switch (machine) {
case PMDK_MACHINE_X86_64:
return "AMD X86-64";
case PMDK_MACHINE_AARCH64:
return "Aarch64";
}
int ret = snprintf(str_buff, STR_MAX, "unknown %u", machine);
if (ret < 0 || ret >= STR_MAX)
return "unknown";
return str_buff;
}
const char *
out_get_alignment_desc_str(uint64_t ad, uint64_t valid_ad)
{
static char str_buff[STR_MAX] = {0, };
int ret = 0;
if (ad == valid_ad)
ret = snprintf(str_buff, STR_MAX, "0x%016"PRIx64"[OK]", ad);
else
ret = snprintf(str_buff, STR_MAX, "0x%016"PRIx64" "
"[wrong! should be 0x%016"PRIx64"]", ad, valid_ad);
if (ret < 0 || ret >= STR_MAX)
return "";
return str_buff;
}
const char *
out_get_incompat_features_str(uint32_t incompat)
{
static char str_buff[STR_MAX] = {0};
int ret = 0;
if (incompat == 0) {
return "0x0";
} else {
ret = snprintf(str_buff, STR_MAX, "0x%x [", incompat);
if (ret < 0 || ret >= STR_MAX) {
ERR("!snprintf for incompat features");
return "<error>";
}
int count = 0;
int curr = ret;
if (incompat & POOL_FEAT_NOHDRS) {
ret = snprintf(str_buff + curr,
(size_t)(STR_MAX - curr), "%s", "NOHDRS");
if (ret < 0 || curr + ret >= STR_MAX)
return "";
curr += ret;
++count;
incompat &= (uint32_t)(~(POOL_FEAT_NOHDRS));
}
if (incompat > 0) {
ret = snprintf(str_buff + curr,
(size_t)(STR_MAX - curr), "%s%s",
count ? ", " : "", "?UNKNOWN_FLAG?");
if (ret < 0 || curr + ret >= STR_MAX)
return "";
curr += ret;
}
ret = snprintf(str_buff + curr, (size_t)(STR_MAX - curr), "]");
if (ret < 0 || curr + ret >= STR_MAX)
return "";
}
return str_buff;
}