blob: 968c82ab64a9ad0bbf04f32769dea1362848b842 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
#include <stdio.h>
#include <unistd.h>
#include <getopt.h>
#include <stdlib.h>
#include <string.h>
#include "log-writes.h"
enum option_indexes {
NEXT_FLUSH,
NEXT_FUA,
START_ENTRY,
END_MARK,
LOG,
REPLAY,
LIMIT,
VERBOSE,
FIND,
NUM_ENTRIES,
NO_DISCARD,
FSCK,
CHECK,
START_MARK,
START_SECTOR,
END_SECTOR,
};
static struct option long_options[] = {
{"next-flush", no_argument, NULL, 0},
{"next-fua", no_argument, NULL, 0},
{"start-entry", required_argument, NULL, 0},
{"end-mark", required_argument, NULL, 0},
{"log", required_argument, NULL, 0},
{"replay", required_argument, NULL, 0},
{"limit", required_argument, NULL, 0},
{"verbose", no_argument, NULL, 'v'},
{"find", no_argument, NULL, 0},
{"num-entries", no_argument, NULL, 0},
{"no-discard", no_argument, NULL, 0},
{"fsck", required_argument, NULL, 0},
{"check", required_argument, NULL, 0},
{"start-mark", required_argument, NULL, 0},
{"start-sector", required_argument, NULL, 0},
{"end-sector", required_argument, NULL, 0},
{ NULL, 0, NULL, 0 },
};
static void usage(void)
{
fprintf(stderr, "Usage: replay-log --log <logfile> [options]\n");
fprintf(stderr, "\t--replay <device> - replay onto a specific "
"device\n");
fprintf(stderr, "\t--limit <number> - number of entries to replay\n");
fprintf(stderr, "\t--next-flush - replay to/find the next flush\n");
fprintf(stderr, "\t--next-fua - replay to/find the next fua\n");
fprintf(stderr, "\t--start-entry <entry> - start at the given "
"entry #\n");
fprintf(stderr, "\t--start-mark <mark> - mark to start from\n");
fprintf(stderr, "\t--end-mark <mark> - replay to/find the given mark\n");
fprintf(stderr, "\t--find - put replay-log in find mode, will search "
"based on the other options\n");
fprintf(stderr, "\t--number-entries - print the number of entries in "
"the log\n");
fprintf(stderr, "\t--no-discard - don't process discard entries\n");
fprintf(stderr, "\t--fsck - the fsck command to run, must specify "
"--check\n");
fprintf(stderr, "\t--check [<number>|flush|fua|discard] when to check "
"the file system, mush specify --fsck\n");
fprintf(stderr, "\t--start-sector <sector> - replay ops on region "
"from <sector> onto <device>\n");
fprintf(stderr, "\t--end-sector <sector> - replay ops on region "
"to <sector> onto <device>\n");
fprintf(stderr, "\t-v or --verbose - print replayed ops\n");
fprintf(stderr, "\t-vv - print also skipped ops\n");
exit(1);
}
/*
* Check if the log entry flag matches one of the stop_flags.
* If stop_flag has LOG_MARK, then looking also for match of
* the mark label.
*/
static int should_stop(struct log_write_entry *entry, u64 stop_flags,
char *mark)
{
u64 flags = le64_to_cpu(entry->flags);
int check_mark = (stop_flags & LOG_MARK_FLAG);
/* mark data begins after entry header */
char *buf = entry->data;
/* entry buffer is padded with at least 1 zero after data_len */
u64 buflen = le64_to_cpu(entry->data_len) + 1;
if (flags & stop_flags) {
if (!check_mark)
return 1;
if ((flags & LOG_MARK_FLAG) &&
!strncmp(mark, buf, buflen))
return 1;
}
return 0;
}
static int run_fsck(struct log *log, char *fsck_command)
{
int ret = fsync(log->replayfd);
if (ret)
return ret;
ret = system(fsck_command);
if (ret >= 0)
ret = WEXITSTATUS(ret);
return ret ? -1 : 0;
}
enum log_replay_check_mode {
CHECK_NUMBER = 1,
CHECK_FUA = 2,
CHECK_FLUSH = 3,
CHECK_DISCARD = 4,
};
static int seek_to_mark(struct log *log, struct log_write_entry *entry,
char *mark)
{
int ret;
while ((ret = log_seek_next_entry(log, entry, 1)) == 0) {
if (should_stop(entry, LOG_MARK_FLAG, mark))
break;
}
if (ret == 1) {
fprintf(stderr, "Couldn't find starting mark\n");
ret = -1;
}
return ret;
}
int main(int argc, char **argv)
{
char *logfile = NULL, *replayfile = NULL, *fsck_command = NULL;
struct log_write_entry *entry;
u64 stop_flags = 0;
u64 start_entry = 0;
u64 start_sector = 0;
u64 end_sector = -1ULL;
u64 run_limit = 0;
u64 num_entries = 0;
u64 check_number = 0;
char *end_mark = NULL, *start_mark = NULL;
char *tmp = NULL;
struct log *log;
int find_mode = 0;
int c;
int opt_index;
int ret;
int print_num_entries = 0;
int discard = 1;
enum log_replay_check_mode check_mode = 0;
while ((c = getopt_long(argc, argv, "v", long_options,
&opt_index)) >= 0) {
switch(c) {
case 'v':
log_writes_verbose++;
continue;
case '?':
usage();
default:
break;
}
switch(opt_index) {
case NEXT_FLUSH:
stop_flags |= LOG_FLUSH_FLAG;
break;
case NEXT_FUA:
stop_flags |= LOG_FUA_FLAG;
break;
case START_ENTRY:
start_entry = strtoull(optarg, &tmp, 0);
if (tmp && *tmp != '\0') {
fprintf(stderr, "Invalid entry number\n");
exit(1);
}
tmp = NULL;
break;
case START_MARK:
/*
* Biggest sectorsize is 4k atm, so limit the mark to 4k
* minus the size of the entry. Say 4097 since we want
* an extra slot for \0.
*/
start_mark = strndup(optarg, 4097 -
sizeof(struct log_write_entry));
if (!start_mark) {
fprintf(stderr, "Couldn't allocate memory\n");
exit(1);
}
break;
case END_MARK:
/*
* Biggest sectorsize is 4k atm, so limit the mark to 4k
* minus the size of the entry. Say 4097 since we want
* an extra slot for \0.
*/
end_mark = strndup(optarg, 4097 -
sizeof(struct log_write_entry));
if (!end_mark) {
fprintf(stderr, "Couldn't allocate memory\n");
exit(1);
}
stop_flags |= LOG_MARK_FLAG;
break;
case LOG:
logfile = strdup(optarg);
if (!logfile) {
fprintf(stderr, "Couldn't allocate memory\n");
exit(1);
}
break;
case REPLAY:
replayfile = strdup(optarg);
if (!replayfile) {
fprintf(stderr, "Couldn't allocate memory\n");
exit(1);
}
break;
case LIMIT:
run_limit = strtoull(optarg, &tmp, 0);
if (tmp && *tmp != '\0') {
fprintf(stderr, "Invalid entry number\n");
exit(1);
}
tmp = NULL;
break;
case FIND:
find_mode = 1;
break;
case NUM_ENTRIES:
print_num_entries = 1;
break;
case NO_DISCARD:
discard = 0;
break;
case FSCK:
fsck_command = strdup(optarg);
if (!fsck_command) {
fprintf(stderr, "Couldn't allocate memory\n");
exit(1);
}
break;
case CHECK:
if (!strcmp(optarg, "flush")) {
check_mode = CHECK_FLUSH;
} else if (!strcmp(optarg, "fua")) {
check_mode = CHECK_FUA;
} else if (!strcmp(optarg, "discard")) {
check_mode = CHECK_DISCARD;
} else {
check_mode = CHECK_NUMBER;
check_number = strtoull(optarg, &tmp, 0);
if (!check_number || (tmp && *tmp != '\0')) {
fprintf(stderr,
"Invalid entry number\n");
exit(1);
}
tmp = NULL;
}
break;
case START_SECTOR:
start_sector = strtoull(optarg, &tmp, 0);
if (tmp && *tmp != '\0') {
fprintf(stderr, "Invalid sector number\n");
exit(1);
}
tmp = NULL;
break;
case END_SECTOR:
end_sector = strtoull(optarg, &tmp, 0);
if (tmp && *tmp != '\0') {
fprintf(stderr, "Invalid sector number\n");
exit(1);
}
tmp = NULL;
break;
default:
usage();
}
}
if (!logfile)
usage();
log = log_open(logfile, replayfile);
if (!log)
exit(1);
free(logfile);
free(replayfile);
if (!discard)
log->flags |= LOG_IGNORE_DISCARD;
log->start_sector = start_sector;
log->end_sector = end_sector;
entry = malloc(log->sectorsize);
if (!entry) {
fprintf(stderr, "Couldn't allocate buffer\n");
log_free(log);
exit(1);
}
if (start_mark) {
ret = seek_to_mark(log, entry, start_mark);
if (ret)
exit(1);
free(start_mark);
} else {
ret = log_seek_entry(log, start_entry);
if (ret)
exit(1);
}
if ((fsck_command && !check_mode) || (!fsck_command && check_mode))
usage();
/* We just want to find a given entry */
if (find_mode) {
while ((ret = log_seek_next_entry(log, entry, 1)) == 0) {
num_entries++;
if ((run_limit && num_entries == run_limit) ||
should_stop(entry, stop_flags, end_mark)) {
printf("%llu@%llu\n",
(unsigned long long)log->cur_entry - 1,
log->cur_pos / log->sectorsize);
log_free(log);
return 0;
}
}
log_free(log);
if (ret < 0)
return ret;
fprintf(stderr, "Couldn't find entry\n");
return 1;
}
/* Used for scripts, just print the number of entries in the log */
if (print_num_entries) {
printf("%llu\n", (unsigned long long)log->nr_entries);
log_free(log);
return 0;
}
/* No replay, just spit out the log info. */
if (!replayfile) {
printf("Log version=%d, sectorsize=%lu, entries=%llu\n",
WRITE_LOG_VERSION, (unsigned long)log->sectorsize,
(unsigned long long)log->nr_entries);
log_free(log);
return 0;
}
while ((ret = log_replay_next_entry(log, entry, 1)) == 0) {
num_entries++;
if (fsck_command) {
if ((check_mode == CHECK_NUMBER) &&
!(num_entries % check_number))
ret = run_fsck(log, fsck_command);
else if ((check_mode == CHECK_FUA) &&
should_stop(entry, LOG_FUA_FLAG, NULL))
ret = run_fsck(log, fsck_command);
else if ((check_mode == CHECK_FLUSH) &&
should_stop(entry, LOG_FLUSH_FLAG, NULL))
ret = run_fsck(log, fsck_command);
else if ((check_mode == CHECK_DISCARD) &&
should_stop(entry, LOG_DISCARD_FLAG, NULL))
ret = run_fsck(log, fsck_command);
else
ret = 0;
if (ret) {
fprintf(stderr, "Fsck errored out on entry "
"%llu\n",
(unsigned long long)log->cur_entry - 1);
break;
}
}
if ((run_limit && num_entries == run_limit) ||
should_stop(entry, stop_flags, end_mark))
break;
}
fsync(log->replayfd);
log_free(log);
free(end_mark);
if (ret < 0)
exit(1);
return 0;
}