#include <stdio.h>
#include <stdint.h>
#include "out.h"
#include "libpmempool.h"
#include "pmempool.h"
#include "pool.h"
#include "check_util.h"
#define CHECK_END UINT_MAX
#define MSG_SEPARATOR '|'
#define MSG_PLACE_OF_SEPARATION '.'
#define MAX_MSG_STR_SIZE 8192
#define CHECK_ANSWER_YES "yes"
#define CHECK_ANSWER_NO "no"
#define STR_MAX 256
#define TIME_STR_FMT "%a %b %d %Y %H:%M:%S"
#define UUID_STR_MAX 37
enum check_answer {
PMEMPOOL_CHECK_ANSWER_EMPTY,
PMEMPOOL_CHECK_ANSWER_YES,
PMEMPOOL_CHECK_ANSWER_NO,
PMEMPOOL_CHECK_ANSWER_DEFAULT,
};
struct check_status {
TAILQ_ENTRY(check_status) next;
struct pmempool_check_status status;
unsigned question;
enum check_answer answer;
char *msg;
};
TAILQ_HEAD(check_status_head, check_status);
struct check_data {
unsigned step;
location step_data;
struct check_status *error;
struct check_status_head infos;
struct check_status_head questions;
struct check_status_head answers;
struct check_status *check_status_cache;
};
struct check_data *
check_data_alloc(void)
{
LOG(3, NULL);
struct check_data *data = malloc(sizeof(*data));
if (data == NULL) {
ERR("!malloc");
return NULL;
}
memset(&data->step_data, 0, sizeof(location));
data->check_status_cache = NULL;
data->error = NULL;
data->step = 0;
TAILQ_INIT(&data->infos);
TAILQ_INIT(&data->questions);
TAILQ_INIT(&data->answers);
return data;
}
void
check_data_free(struct check_data *data)
{
LOG(3, NULL);
if (data->error != NULL) {
free(data->error);
data->error = NULL;
}
if (data->check_status_cache != NULL) {
free(data->check_status_cache);
data->check_status_cache = NULL;
}
while (!TAILQ_EMPTY(&data->infos)) {
struct check_status *statp = TAILQ_FIRST(&data->infos);
TAILQ_REMOVE(&data->infos, statp, next);
free(statp);
}
while (!TAILQ_EMPTY(&data->questions)) {
struct check_status *statp = TAILQ_FIRST(&data->questions);
TAILQ_REMOVE(&data->questions, statp, next);
free(statp);
}
while (!TAILQ_EMPTY(&data->answers)) {
struct check_status *statp = TAILQ_FIRST(&data->answers);
TAILQ_REMOVE(&data->answers, statp, next);
free(statp);
}
free(data);
}
uint32_t
check_step_get(struct check_data *data)
{
return data->step;
}
void
check_step_inc(struct check_data *data)
{
if (check_is_end_util(data))
return;
++data->step;
memset(&data->step_data, 0, sizeof(location));
}
location *
check_get_step_data(struct check_data *data)
{
return &data->step_data;
}
void
check_end(struct check_data *data)
{
LOG(3, NULL);
data->step = CHECK_END;
}
int
check_is_end_util(struct check_data *data)
{
return data->step == CHECK_END;
}
static inline struct check_status *
status_alloc(void)
{
struct check_status *status = malloc(sizeof(*status));
if (!status)
FATAL("!malloc");
status->msg = malloc(sizeof(char) * MAX_MSG_STR_SIZE);
if (!status->msg) {
free(status);
FATAL("!malloc");
}
status->status.str.msg = status->msg;
status->answer = PMEMPOOL_CHECK_ANSWER_EMPTY;
status->question = CHECK_INVALID_QUESTION;
return status;
}
static void
status_release(struct check_status *status)
{
#ifdef _WIN32
if (status->status.str.msg != status->msg)
free((void *)status->status.str.msg);
#endif
free(status->msg);
free(status);
}
static inline int
status_msg_info_only(const char *msg)
{
char *sep = strchr(msg, MSG_SEPARATOR);
if (sep) {
ASSERTne(sep, msg);
--sep;
ASSERTeq(*sep, MSG_PLACE_OF_SEPARATION);
*sep = '\0';
return 0;
}
return -1;
}
static inline int
status_msg_info_and_question(const char *msg)
{
char *sep = strchr(msg, MSG_SEPARATOR);
if (sep) {
*sep = ' ';
return 0;
}
return -1;
}
static int
status_push(PMEMpoolcheck *ppc, struct check_status *st, uint32_t question)
{
if (st->status.type == PMEMPOOL_CHECK_MSG_TYPE_ERROR) {
ASSERTeq(ppc->data->error, NULL);
ppc->data->error = st;
return -1;
} else if (st->status.type == PMEMPOOL_CHECK_MSG_TYPE_INFO) {
if (CHECK_IS(ppc, VERBOSE))
TAILQ_INSERT_TAIL(&ppc->data->infos, st, next);
else
check_status_release(ppc, st);
return 0;
}
if (CHECK_IS_NOT(ppc, REPAIR)) {
if (status_msg_info_only(st->msg)) {
ERR("no error message for the user");
st->msg[0] = '\0';
}
st->status.type = PMEMPOOL_CHECK_MSG_TYPE_ERROR;
return status_push(ppc, st, question);
}
if (CHECK_IS(ppc, ALWAYS_YES)) {
if (!status_msg_info_only(st->msg)) {
st->status.type = PMEMPOOL_CHECK_MSG_TYPE_INFO;
status_push(ppc, st, question);
st = status_alloc();
}
ppc->result = CHECK_RESULT_PROCESS_ANSWERS;
st->question = question;
st->answer = PMEMPOOL_CHECK_ANSWER_YES;
st->status.type = PMEMPOOL_CHECK_MSG_TYPE_QUESTION;
TAILQ_INSERT_TAIL(&ppc->data->answers, st, next);
} else {
status_msg_info_and_question(st->msg);
st->question = question;
ppc->result = CHECK_RESULT_ASK_QUESTIONS;
st->answer = PMEMPOOL_CHECK_ANSWER_EMPTY;
TAILQ_INSERT_TAIL(&ppc->data->questions, st, next);
}
return 0;
}
int
check_status_create(PMEMpoolcheck *ppc, enum pmempool_check_msg_type type,
uint32_t question, const char *fmt, ...)
{
if (CHECK_IS_NOT(ppc, VERBOSE) && type == PMEMPOOL_CHECK_MSG_TYPE_INFO)
return 0;
struct check_status *st = status_alloc();
ASSERT(CHECK_IS(ppc, FORMAT_STR));
va_list ap;
va_start(ap, fmt);
int p = vsnprintf(st->msg, MAX_MSG_STR_SIZE, fmt, ap);
va_end(ap);
if (type != PMEMPOOL_CHECK_MSG_TYPE_QUESTION && errno &&
p > 0) {
char buff[UTIL_MAX_ERR_MSG];
util_strerror(errno, buff, UTIL_MAX_ERR_MSG);
int ret = snprintf(st->msg + p, MAX_MSG_STR_SIZE - (size_t)p,
": %s", buff);
if (ret < 0 || ret >= (int)(MAX_MSG_STR_SIZE - (size_t)p)) {
ERR("!snprintf");
free(st);
return -1;
}
}
st->status.type = type;
return status_push(ppc, st, question);
}
void
check_status_release(PMEMpoolcheck *ppc, struct check_status *status)
{
if (status->status.type == PMEMPOOL_CHECK_MSG_TYPE_ERROR)
ppc->data->error = NULL;
status_release(status);
}
static struct check_status *
pop_status(struct check_data *data, struct check_status_head *queue)
{
if (!TAILQ_EMPTY(queue)) {
ASSERTeq(data->check_status_cache, NULL);
data->check_status_cache = TAILQ_FIRST(queue);
TAILQ_REMOVE(queue, data->check_status_cache, next);
return data->check_status_cache;
}
return NULL;
}
struct check_status *
check_pop_question(struct check_data *data)
{
return pop_status(data, &data->questions);
}
struct check_status *
check_pop_info(struct check_data *data)
{
return pop_status(data, &data->infos);
}
struct check_status *
check_pop_error(struct check_data *data)
{
if (data->error) {
ASSERTeq(data->check_status_cache, NULL);
data->check_status_cache = data->error;
data->error = NULL;
return data->check_status_cache;
}
return NULL;
}
#ifdef _WIN32
void
cache_to_utf8(struct check_data *data, char *buf, size_t size)
{
if (data->check_status_cache == NULL)
return;
struct check_status *status = data->check_status_cache;
if (status->status.type == PMEMPOOL_CHECK_MSG_TYPE_QUESTION) {
struct pmempool_check_statusW *wstatus =
(struct pmempool_check_statusW *)&status->status;
wchar_t *wstring = (wchar_t *)wstatus->str.msg;
status->status.str.msg = util_toUTF8(wstring);
if (status->status.str.msg == NULL)
FATAL("!malloc");
util_free_UTF16(wstring);
if (util_toUTF8_buff(wstatus->str.answer, buf, size) != 0)
FATAL("Invalid answer conversion %s",
out_get_errormsg());
status->status.str.answer = buf;
}
}
#endif
void
check_clear_status_cache(struct check_data *data)
{
if (data->check_status_cache) {
switch (data->check_status_cache->status.type) {
case PMEMPOOL_CHECK_MSG_TYPE_INFO:
case PMEMPOOL_CHECK_MSG_TYPE_ERROR:
status_release(data->check_status_cache);
data->check_status_cache = NULL;
break;
case PMEMPOOL_CHECK_MSG_TYPE_QUESTION:
break;
default:
ASSERT(0);
}
}
}
static void
status_answer_push(struct check_data *data, struct check_status *st)
{
ASSERTeq(st->status.type, PMEMPOOL_CHECK_MSG_TYPE_QUESTION);
TAILQ_INSERT_TAIL(&data->answers, st, next);
}
int
check_push_answer(PMEMpoolcheck *ppc)
{
if (ppc->data->check_status_cache == NULL)
return 0;
struct check_status *status = ppc->data->check_status_cache;
if (status->status.str.answer != NULL) {
if (strcmp(status->status.str.answer, CHECK_ANSWER_YES) == 0)
status->answer = PMEMPOOL_CHECK_ANSWER_YES;
else if (strcmp(status->status.str.answer, CHECK_ANSWER_NO)
== 0)
status->answer = PMEMPOOL_CHECK_ANSWER_NO;
}
if (status->answer == PMEMPOOL_CHECK_ANSWER_EMPTY) {
status_answer_push(ppc->data, ppc->data->check_status_cache);
ppc->data->check_status_cache = NULL;
CHECK_INFO(ppc, "Answer must be either %s or %s",
CHECK_ANSWER_YES, CHECK_ANSWER_NO);
return -1;
}
TAILQ_INSERT_TAIL(&ppc->data->answers,
ppc->data->check_status_cache, next);
ppc->data->check_status_cache = NULL;
return 0;
}
bool
check_has_error(struct check_data *data)
{
return data->error != NULL;
}
bool
check_has_answer(struct check_data *data)
{
return !TAILQ_EMPTY(&data->answers);
}
static struct check_status *
pop_answer(struct check_data *data)
{
struct check_status *ret = NULL;
if (!TAILQ_EMPTY(&data->answers)) {
ret = TAILQ_FIRST(&data->answers);
TAILQ_REMOVE(&data->answers, ret, next);
}
return ret;
}
struct pmempool_check_status *
check_status_get_util(struct check_status *status)
{
return &status->status;
}
int
check_answer_loop(PMEMpoolcheck *ppc, location *data, void *ctx,
int (*callback)(PMEMpoolcheck *, location *, uint32_t, void *ctx))
{
struct check_status *answer;
while ((answer = pop_answer(ppc->data)) != NULL) {
if (answer->answer != PMEMPOOL_CHECK_ANSWER_YES) {
CHECK_ERR(ppc,
"cannot complete repair, reverting changes");
ppc->result = CHECK_RESULT_NOT_CONSISTENT;
goto error;
}
if (callback(ppc, data, answer->question, ctx)) {
ppc->result = CHECK_RESULT_CANNOT_REPAIR;
goto error;
}
if (ppc->result == CHECK_RESULT_ERROR)
goto error;
ppc->result = CHECK_RESULT_REPAIRED;
check_status_release(ppc, answer);
}
return 0;
error:
check_status_release(ppc, answer);
return -1;
}
int
check_questions_sequence_validate(PMEMpoolcheck *ppc)
{
ASSERT(ppc->result == CHECK_RESULT_CONSISTENT ||
ppc->result == CHECK_RESULT_ASK_QUESTIONS ||
ppc->result == CHECK_RESULT_PROCESS_ANSWERS ||
ppc->result == CHECK_RESULT_REPAIRED);
if (ppc->result == CHECK_RESULT_ASK_QUESTIONS) {
ASSERT(!TAILQ_EMPTY(&ppc->data->questions));
return -1;
}
return 0;
}
const char *
check_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) {
ERR("failed to get time str");
return "";
}
}
return str_buff;
}
const char *
check_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) {
ERR("failed to covert uuid to string");
return "";
}
return uuid_str;
}
const char *
check_get_pool_type_str(enum pool_type type)
{
switch (type) {
case POOL_TYPE_BTT:
return "btt";
case POOL_TYPE_LOG:
return "pmemlog";
case POOL_TYPE_BLK:
return "pmemblk";
case POOL_TYPE_OBJ:
return "pmemobj";
case POOL_TYPE_CTO:
return "pmemcto";
default:
return "unknown";
}
}
void
check_insert_arena(PMEMpoolcheck *ppc, struct arena *arenap)
{
TAILQ_INSERT_TAIL(&ppc->pool->arenas, arenap, next);
ppc->pool->narenas++;
}