mirror of
https://oauth2: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@github.com/m1ngsama/TNT.git
synced 2026-06-26 04:34:38 +08:00
Add offline message log recovery modes
This commit is contained in:
parent
3252e4583c
commit
1c451b7722
16 changed files with 382 additions and 2 deletions
3
Makefile
3
Makefile
|
|
@ -120,9 +120,10 @@ unit-test:
|
|||
@echo "Running unit tests..."
|
||||
@$(MAKE) -C tests/unit run
|
||||
|
||||
script-test:
|
||||
script-test: all
|
||||
@echo "Running script tests..."
|
||||
@cd tests && ./test_logrotate.sh
|
||||
@cd tests && ./test_message_log_tool.sh
|
||||
|
||||
integration-test: all
|
||||
@echo "Running integration tests..."
|
||||
|
|
|
|||
11
README.md
11
README.md
|
|
@ -230,6 +230,17 @@ The script archives the full log, keeps the last `KEEP_LINES` records in the
|
|||
active file, compresses the archive when `gzip` is available, and can be
|
||||
previewed with `--dry-run`.
|
||||
|
||||
Installed binaries also include offline checks for the v1 log format:
|
||||
|
||||
```sh
|
||||
tnt --log-check /var/lib/tnt/messages.log
|
||||
tnt --log-recover /var/lib/tnt/messages.log > messages.recovered.log
|
||||
```
|
||||
|
||||
`--log-check` prints record counts and exits non-zero when invalid records are
|
||||
found. `--log-recover` writes valid records to stdout and reports skipped
|
||||
records to stderr; it never edits the source log in place.
|
||||
|
||||
## Development
|
||||
|
||||
### Building
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@
|
|||
exporting valid persisted `messages.log` v1 records.
|
||||
- Added regression-tested manual log archive and compaction coverage for
|
||||
`scripts/logrotate.sh`.
|
||||
- Added offline `tnt --log-check` and `tnt --log-recover` modes for auditing
|
||||
and recovering valid `messages.log` v1 records without editing the source
|
||||
log in place.
|
||||
- Added a public security policy, supported-version guidance, and GitHub issue
|
||||
templates for bug reports and feature requests.
|
||||
- Added `tntctl`, a thin local wrapper around the documented SSH exec
|
||||
|
|
@ -65,6 +68,9 @@
|
|||
test in the normal test suite.
|
||||
- `messages.log` v1 record parsing and formatting now live in a dedicated
|
||||
`message_log` module instead of being embedded in `message.c`.
|
||||
- Offline message-log recovery shares the same `message_log` parser used by
|
||||
replay, search, and `dump`, so recovery behavior follows the documented v1
|
||||
contract.
|
||||
- The two-user lifecycle test now covers opening `:inbox` before a private
|
||||
message arrives, matching the way users often leave an inbox page open.
|
||||
- Private-message inbox access now uses its own mutex instead of sharing the
|
||||
|
|
|
|||
|
|
@ -125,6 +125,16 @@ the archive when `gzip` is available, and keeps the newest five archives by
|
|||
default. Use `--dry-run` to preview actions, or `--keep-archives N` to change
|
||||
archive retention.
|
||||
|
||||
Before replacing a suspicious log, inspect and recover it offline:
|
||||
|
||||
```bash
|
||||
tnt --log-check /var/lib/tnt/messages.log
|
||||
tnt --log-recover /var/lib/tnt/messages.log > /var/lib/tnt/messages.recovered.log
|
||||
```
|
||||
|
||||
`--log-recover` writes valid records to stdout and reports skipped records to
|
||||
stderr. Review the recovered file before replacing the active log.
|
||||
|
||||
## Firewall
|
||||
|
||||
```bash
|
||||
|
|
|
|||
|
|
@ -82,6 +82,7 @@ src/
|
|||
├── chat_room.c - Chat room state, message ring, and update sequence
|
||||
├── message.c - Message persistence (RFC3339 format)
|
||||
├── message_log.c - messages.log v1 parsing and formatting
|
||||
├── message_log_tool.c - Offline messages.log check/recover CLI
|
||||
├── history_view.c - NORMAL-mode scroll window rules
|
||||
├── tui.c - Terminal UI rendering (ANSI escape codes)
|
||||
├── tui_status.c - Mode/status/input-line rendering
|
||||
|
|
@ -105,6 +106,7 @@ include/
|
|||
├── chat_room.h - Chat room interface
|
||||
├── message.h - Message structure and persistence
|
||||
├── message_log.h - messages.log v1 parser/formatter interface
|
||||
├── message_log_tool.h - Offline log check/recover interface
|
||||
├── command_catalog.h - COMMAND-mode command metadata interface
|
||||
├── history_view.h - Scroll-state helpers
|
||||
├── tui.h - TUI rendering functions
|
||||
|
|
|
|||
|
|
@ -75,6 +75,30 @@ the active file to the last `KEEP_LINES` records, compresses the archive when
|
|||
Run it while TNT is stopped or during a quiet maintenance window if strict log
|
||||
consistency matters.
|
||||
|
||||
## Recovery
|
||||
|
||||
Installed `tnt` binaries provide offline log checking and recovery:
|
||||
|
||||
```sh
|
||||
tnt --log-check LOG_FILE
|
||||
tnt --log-recover LOG_FILE > recovered.messages.log
|
||||
```
|
||||
|
||||
`--log-check` prints a summary:
|
||||
|
||||
```text
|
||||
path /var/lib/tnt/messages.log
|
||||
records_seen 120
|
||||
valid_records 119
|
||||
invalid_records 1
|
||||
first_invalid_line 120
|
||||
```
|
||||
|
||||
It exits `0` when every record is valid and `1` when invalid records are found
|
||||
or the log cannot be read. `--log-recover` writes only valid v1 records to
|
||||
stdout, prints the same summary to stderr, and also exits `1` if records were
|
||||
skipped. It never modifies the source log.
|
||||
|
||||
## Compatibility
|
||||
|
||||
The v1 record format is stable for TNT 1.x. Future incompatible storage
|
||||
|
|
|
|||
|
|
@ -59,6 +59,9 @@ MAINTENANCE
|
|||
archive and compact messages.log
|
||||
scripts/logrotate.sh --dry-run ...
|
||||
preview log maintenance actions
|
||||
tnt --log-check LOG_FILE audit messages.log v1 records
|
||||
tnt --log-recover LOG_FILE > OUT
|
||||
write valid records to stdout
|
||||
|
||||
STRUCTURE
|
||||
src/main.c entry, signals
|
||||
|
|
@ -72,6 +75,7 @@ STRUCTURE
|
|||
src/exec.c SSH exec command dispatch
|
||||
src/message.c persistence, search
|
||||
src/message_log.c messages.log v1 parsing and formatting
|
||||
src/message_log_tool.c offline messages.log check/recover CLI
|
||||
src/history_view.c message viewport / scroll state
|
||||
src/help_text.c full-screen key reference text
|
||||
src/manual.c concise manual panel rendering
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ Goal: make stored history durable, inspectable, and recoverable.
|
|||
- ✅ validate persisted UTF-8 and record structure before replay/search
|
||||
- ✅ provide an inspection/export command for persisted records
|
||||
- ✅ add log rotation and compaction tooling
|
||||
- define broader recovery tooling for truncated or partially corrupted logs
|
||||
- ✅ define broader recovery tooling for truncated or partially corrupted logs
|
||||
|
||||
## Stage 4: Interactive UX
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ void cli_text_append_help(char *buffer, size_t buf_size, size_t *pos,
|
|||
const char *program_name, ui_lang_t lang);
|
||||
const char *cli_text_invalid_port_format(ui_lang_t lang);
|
||||
const char *cli_text_invalid_value_format(ui_lang_t lang);
|
||||
const char *cli_text_option_requires_arg_format(ui_lang_t lang);
|
||||
const char *cli_text_unknown_option_format(ui_lang_t lang);
|
||||
const char *cli_text_short_usage_format(ui_lang_t lang);
|
||||
|
||||
|
|
|
|||
9
include/message_log_tool.h
Normal file
9
include/message_log_tool.h
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
#ifndef MESSAGE_LOG_TOOL_H
|
||||
#define MESSAGE_LOG_TOOL_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
int message_log_tool_check(const char *path);
|
||||
int message_log_tool_recover(const char *path);
|
||||
|
||||
#endif /* MESSAGE_LOG_TOOL_H */
|
||||
|
|
@ -18,6 +18,8 @@ void cli_text_append_help(char *buffer, size_t buf_size, size_t *pos,
|
|||
" --rate-limit 0|1 Disable/enable rate-based blocking\n"
|
||||
" --idle-timeout SECONDS Idle disconnect timeout\n"
|
||||
" --ssh-log-level LEVEL libssh log level 0..4\n"
|
||||
" --log-check FILE Check messages.log v1 records\n"
|
||||
" --log-recover FILE Write valid records to stdout\n"
|
||||
" -V, --version Show version\n"
|
||||
" -h, --help Show this help\n"
|
||||
"\n"
|
||||
|
|
@ -42,6 +44,8 @@ void cli_text_append_help(char *buffer, size_t buf_size, size_t *pos,
|
|||
" --rate-limit 0|1 禁用/启用速率封禁\n"
|
||||
" --idle-timeout SECONDS 空闲断开时间\n"
|
||||
" --ssh-log-level LEVEL libssh 日志级别 0..4\n"
|
||||
" --log-check FILE 检查 messages.log v1 记录\n"
|
||||
" --log-recover FILE 将有效记录写入 stdout\n"
|
||||
" -V, --version 显示版本\n"
|
||||
" -h, --help 显示此帮助\n"
|
||||
"\n"
|
||||
|
|
@ -74,6 +78,13 @@ const char *cli_text_invalid_value_format(ui_lang_t lang) {
|
|||
return i18n_string(text, lang);
|
||||
}
|
||||
|
||||
const char *cli_text_option_requires_arg_format(ui_lang_t lang) {
|
||||
static const i18n_string_t text =
|
||||
I18N_STRING("Option requires argument: %s\n",
|
||||
"选项需要参数: %s\n");
|
||||
return i18n_string(text, lang);
|
||||
}
|
||||
|
||||
const char *cli_text_unknown_option_format(ui_lang_t lang) {
|
||||
static const i18n_string_t text =
|
||||
I18N_STRING("Unknown option: %s\n", "未知选项: %s\n");
|
||||
|
|
|
|||
29
src/main.c
29
src/main.c
|
|
@ -3,6 +3,7 @@
|
|||
#include "common.h"
|
||||
#include "i18n.h"
|
||||
#include "message.h"
|
||||
#include "message_log_tool.h"
|
||||
#include "ssh_server.h"
|
||||
#include <signal.h>
|
||||
#include <unistd.h>
|
||||
|
|
@ -77,6 +78,8 @@ static int set_numeric_env_option(const char *env_name, const char *opt_name,
|
|||
int main(int argc, char **argv) {
|
||||
int port = DEFAULT_PORT;
|
||||
ui_lang_t lang = i18n_default_ui_lang();
|
||||
const char *log_check_path = NULL;
|
||||
const char *log_recover_path = NULL;
|
||||
|
||||
/* Environment provides defaults; command-line flags override it. */
|
||||
const char *port_env = getenv("PORT");
|
||||
|
|
@ -179,6 +182,20 @@ int main(int argc, char **argv) {
|
|||
return rc;
|
||||
}
|
||||
i++;
|
||||
} else if (strcmp(argv[i], "--log-check") == 0) {
|
||||
if (i + 1 >= argc || argv[i + 1][0] == '\0') {
|
||||
fprintf(stderr, cli_text_option_requires_arg_format(lang),
|
||||
argv[i]);
|
||||
return TNT_EXIT_USAGE;
|
||||
}
|
||||
log_check_path = argv[++i];
|
||||
} else if (strcmp(argv[i], "--log-recover") == 0) {
|
||||
if (i + 1 >= argc || argv[i + 1][0] == '\0') {
|
||||
fprintf(stderr, cli_text_option_requires_arg_format(lang),
|
||||
argv[i]);
|
||||
return TNT_EXIT_USAGE;
|
||||
}
|
||||
log_recover_path = argv[++i];
|
||||
} else if (strcmp(argv[i], "-V") == 0 || strcmp(argv[i], "--version") == 0) {
|
||||
printf("tnt %s\n", TNT_VERSION);
|
||||
return TNT_EXIT_OK;
|
||||
|
|
@ -196,6 +213,18 @@ int main(int argc, char **argv) {
|
|||
}
|
||||
}
|
||||
|
||||
if (log_check_path && log_recover_path) {
|
||||
fprintf(stderr, cli_text_invalid_value_format(lang),
|
||||
"--log-check", "--log-recover");
|
||||
return TNT_EXIT_USAGE;
|
||||
}
|
||||
if (log_check_path) {
|
||||
return message_log_tool_check(log_check_path);
|
||||
}
|
||||
if (log_recover_path) {
|
||||
return message_log_tool_recover(log_recover_path);
|
||||
}
|
||||
|
||||
/* Setup signal handlers */
|
||||
signal(SIGINT, signal_handler);
|
||||
signal(SIGTERM, signal_handler);
|
||||
|
|
|
|||
111
src/message_log_tool.c
Normal file
111
src/message_log_tool.c
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
#include "message_log_tool.h"
|
||||
|
||||
#include "message_log.h"
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
typedef struct {
|
||||
long records_seen;
|
||||
long valid_records;
|
||||
long invalid_records;
|
||||
long first_invalid_line;
|
||||
} message_log_report_t;
|
||||
|
||||
static void discard_line_remainder(FILE *fp) {
|
||||
int c;
|
||||
|
||||
while ((c = fgetc(fp)) != '\n' && c != EOF) {
|
||||
}
|
||||
}
|
||||
|
||||
static int print_recovered_record(const message_t *msg) {
|
||||
char record[MAX_USERNAME_LEN + MAX_MESSAGE_LEN + 48];
|
||||
size_t record_len = 0;
|
||||
|
||||
if (message_log_format_record(msg, record, sizeof(record),
|
||||
&record_len) < 0) {
|
||||
return -1;
|
||||
}
|
||||
return fwrite(record, 1, record_len, stdout) == record_len ? 0 : -1;
|
||||
}
|
||||
|
||||
static void print_report(FILE *stream, const char *path,
|
||||
const message_log_report_t *report) {
|
||||
fprintf(stream,
|
||||
"path %s\n"
|
||||
"records_seen %ld\n"
|
||||
"valid_records %ld\n"
|
||||
"invalid_records %ld\n"
|
||||
"first_invalid_line %ld\n",
|
||||
path,
|
||||
report->records_seen,
|
||||
report->valid_records,
|
||||
report->invalid_records,
|
||||
report->first_invalid_line);
|
||||
}
|
||||
|
||||
static int scan_log(const char *path, bool recover) {
|
||||
FILE *fp;
|
||||
char line[MESSAGE_LOG_MAX_LINE];
|
||||
long line_no = 0;
|
||||
time_t now = time(NULL);
|
||||
message_log_report_t report = {0};
|
||||
|
||||
if (!path || path[0] == '\0') {
|
||||
fprintf(stderr, "log: invalid path\n");
|
||||
return TNT_EXIT_USAGE;
|
||||
}
|
||||
|
||||
fp = fopen(path, "r");
|
||||
if (!fp) {
|
||||
fprintf(stderr, "log: %s: %s\n", path, strerror(errno));
|
||||
return TNT_EXIT_ERROR;
|
||||
}
|
||||
|
||||
while (fgets(line, sizeof(line), fp)) {
|
||||
size_t line_len = strlen(line);
|
||||
message_t parsed;
|
||||
bool valid = false;
|
||||
|
||||
line_no++;
|
||||
report.records_seen++;
|
||||
|
||||
if (line_len >= sizeof(line) - 1 && line[line_len - 1] != '\n') {
|
||||
discard_line_remainder(fp);
|
||||
} else {
|
||||
valid = message_log_parse_record(line, &parsed, now);
|
||||
}
|
||||
|
||||
if (valid) {
|
||||
report.valid_records++;
|
||||
if (recover && print_recovered_record(&parsed) < 0) {
|
||||
fclose(fp);
|
||||
fprintf(stderr, "log: failed to write recovered output\n");
|
||||
return TNT_EXIT_ERROR;
|
||||
}
|
||||
} else {
|
||||
report.invalid_records++;
|
||||
if (report.first_invalid_line == 0) {
|
||||
report.first_invalid_line = line_no;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ferror(fp)) {
|
||||
fclose(fp);
|
||||
fprintf(stderr, "log: failed to read %s\n", path);
|
||||
return TNT_EXIT_ERROR;
|
||||
}
|
||||
fclose(fp);
|
||||
|
||||
print_report(recover ? stderr : stdout, path, &report);
|
||||
return report.invalid_records == 0 ? TNT_EXIT_OK : TNT_EXIT_ERROR;
|
||||
}
|
||||
|
||||
int message_log_tool_check(const char *path) {
|
||||
return scan_log(path, false);
|
||||
}
|
||||
|
||||
int message_log_tool_recover(const char *path) {
|
||||
return scan_log(path, true);
|
||||
}
|
||||
134
tests/test_message_log_tool.sh
Executable file
134
tests/test_message_log_tool.sh
Executable file
|
|
@ -0,0 +1,134 @@
|
|||
#!/bin/sh
|
||||
# Offline messages.log check/recover regression tests.
|
||||
|
||||
set -u
|
||||
|
||||
PASS=0
|
||||
FAIL=0
|
||||
BIN="../tnt"
|
||||
STATE_DIR=$(mktemp -d "${TMPDIR:-/tmp}/tnt-log-tool-test.XXXXXX")
|
||||
|
||||
cleanup() {
|
||||
rm -rf "$STATE_DIR"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
pass() {
|
||||
echo "✓ $1"
|
||||
PASS=$((PASS + 1))
|
||||
}
|
||||
|
||||
fail() {
|
||||
echo "✗ $1"
|
||||
FAIL=$((FAIL + 1))
|
||||
}
|
||||
|
||||
ts_now() {
|
||||
date -u +%Y-%m-%dT%H:%M:%SZ
|
||||
}
|
||||
|
||||
echo "=== TNT Message Log Tool Tests ==="
|
||||
|
||||
if [ ! -x "$BIN" ]; then
|
||||
echo "Error: binary $BIN not found. Run make first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TS=$(ts_now)
|
||||
CLEAN_LOG="$STATE_DIR/clean.log"
|
||||
cat > "$CLEAN_LOG" <<EOF
|
||||
$TS|alice|one
|
||||
$TS|bob|two
|
||||
EOF
|
||||
|
||||
CHECK_OUTPUT=$("$BIN" --log-check "$CLEAN_LOG" 2>&1)
|
||||
CHECK_STATUS=$?
|
||||
printf '%s\n' "$CHECK_OUTPUT" | grep -q '^valid_records 2$' &&
|
||||
printf '%s\n' "$CHECK_OUTPUT" | grep -q '^invalid_records 0$'
|
||||
if [ "$CHECK_STATUS" -eq 0 ] && [ $? -eq 0 ]; then
|
||||
pass "clean log check exits 0"
|
||||
else
|
||||
fail "clean log check"
|
||||
printf '%s\n' "$CHECK_OUTPUT"
|
||||
echo "exit status: $CHECK_STATUS"
|
||||
fi
|
||||
|
||||
BAD_LOG="$STATE_DIR/bad.log"
|
||||
cat > "$BAD_LOG" <<EOF
|
||||
$TS|alice|one
|
||||
$TS|mallory|extra|pipe
|
||||
$TS|bob|two
|
||||
EOF
|
||||
printf '%s|partial|unterminated' "$TS" >> "$BAD_LOG"
|
||||
|
||||
BAD_CHECK_OUTPUT=$("$BIN" --log-check "$BAD_LOG" 2>&1)
|
||||
BAD_CHECK_STATUS=$?
|
||||
printf '%s\n' "$BAD_CHECK_OUTPUT" | grep -q '^records_seen 4$' &&
|
||||
printf '%s\n' "$BAD_CHECK_OUTPUT" | grep -q '^valid_records 2$' &&
|
||||
printf '%s\n' "$BAD_CHECK_OUTPUT" | grep -q '^invalid_records 2$' &&
|
||||
printf '%s\n' "$BAD_CHECK_OUTPUT" | grep -q '^first_invalid_line 2$'
|
||||
if [ "$BAD_CHECK_STATUS" -eq 1 ] && [ $? -eq 0 ]; then
|
||||
pass "bad log check reports skipped records"
|
||||
else
|
||||
fail "bad log check"
|
||||
printf '%s\n' "$BAD_CHECK_OUTPUT"
|
||||
echo "exit status: $BAD_CHECK_STATUS"
|
||||
fi
|
||||
|
||||
RECOVERED="$STATE_DIR/recovered.log"
|
||||
RECOVER_REPORT="$STATE_DIR/recover.report"
|
||||
"$BIN" --log-recover "$BAD_LOG" > "$RECOVERED" 2> "$RECOVER_REPORT"
|
||||
RECOVER_STATUS=$?
|
||||
if [ "$RECOVER_STATUS" -eq 1 ] &&
|
||||
grep -q '^valid_records 2$' "$RECOVER_REPORT" &&
|
||||
grep -q '^invalid_records 2$' "$RECOVER_REPORT" &&
|
||||
grep -q "$TS|alice|one" "$RECOVERED" &&
|
||||
grep -q "$TS|bob|two" "$RECOVERED" &&
|
||||
! grep -q 'mallory' "$RECOVERED" &&
|
||||
! grep -q 'partial' "$RECOVERED"; then
|
||||
pass "recover writes valid records and reports skipped records"
|
||||
else
|
||||
fail "bad log recovery"
|
||||
cat "$RECOVERED" 2>/dev/null
|
||||
cat "$RECOVER_REPORT" 2>/dev/null
|
||||
echo "exit status: $RECOVER_STATUS"
|
||||
fi
|
||||
|
||||
MISSING_OUTPUT=$("$BIN" --log-check "$STATE_DIR/missing.log" 2>&1)
|
||||
MISSING_STATUS=$?
|
||||
if [ "$MISSING_STATUS" -eq 1 ] &&
|
||||
printf '%s\n' "$MISSING_OUTPUT" | grep -q 'No such file'; then
|
||||
pass "missing log exits 1"
|
||||
else
|
||||
fail "missing log handling"
|
||||
printf '%s\n' "$MISSING_OUTPUT"
|
||||
echo "exit status: $MISSING_STATUS"
|
||||
fi
|
||||
|
||||
USAGE_OUTPUT=$("$BIN" --log-check 2>&1)
|
||||
USAGE_STATUS=$?
|
||||
if [ "$USAGE_STATUS" -eq 64 ] &&
|
||||
printf '%s\n' "$USAGE_OUTPUT" | grep -q 'Option requires argument: --log-check'; then
|
||||
pass "missing log-check argument exits 64"
|
||||
else
|
||||
fail "missing log-check argument"
|
||||
printf '%s\n' "$USAGE_OUTPUT"
|
||||
echo "exit status: $USAGE_STATUS"
|
||||
fi
|
||||
|
||||
CONFLICT_OUTPUT=$("$BIN" --log-check "$CLEAN_LOG" --log-recover "$CLEAN_LOG" 2>&1)
|
||||
CONFLICT_STATUS=$?
|
||||
if [ "$CONFLICT_STATUS" -eq 64 ] &&
|
||||
printf '%s\n' "$CONFLICT_OUTPUT" | grep -q 'Invalid --log-check: --log-recover'; then
|
||||
pass "conflicting log modes exit 64"
|
||||
else
|
||||
fail "conflicting log modes"
|
||||
printf '%s\n' "$CONFLICT_OUTPUT"
|
||||
echo "exit status: $CONFLICT_STATUS"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "PASSED: $PASS"
|
||||
echo "FAILED: $FAIL"
|
||||
[ "$FAIL" -eq 0 ] && echo "All tests passed" || echo "Some tests failed"
|
||||
exit "$FAIL"
|
||||
|
|
@ -24,6 +24,8 @@ TEST(help_matches_language) {
|
|||
assert(strstr(output, "Usage: tnt [options]") != NULL);
|
||||
assert(strstr(output, "--bind ADDR") != NULL);
|
||||
assert(strstr(output, "--max-connections N") != NULL);
|
||||
assert(strstr(output, "--log-check FILE") != NULL);
|
||||
assert(strstr(output, "--log-recover FILE") != NULL);
|
||||
assert(strstr(output, "TNT_LANG") != NULL);
|
||||
|
||||
memset(output, 0, sizeof(output));
|
||||
|
|
@ -39,6 +41,7 @@ TEST(help_matches_language) {
|
|||
assert(strstr(output, "[选项]") == NULL);
|
||||
assert(strstr(output, "--public-host HOST") != NULL);
|
||||
assert(strstr(output, "--idle-timeout SECONDS") != NULL);
|
||||
assert(strstr(output, "--log-check FILE") != NULL);
|
||||
assert(strstr(output, "TNT_LANG") != NULL);
|
||||
}
|
||||
|
||||
|
|
@ -51,6 +54,10 @@ TEST(error_formats_match_language) {
|
|||
"Invalid %s: %s\n") == 0);
|
||||
assert(strcmp(cli_text_invalid_value_format(UI_LANG_ZH),
|
||||
"%s 无效: %s\n") == 0);
|
||||
assert(strcmp(cli_text_option_requires_arg_format(UI_LANG_EN),
|
||||
"Option requires argument: %s\n") == 0);
|
||||
assert(strcmp(cli_text_option_requires_arg_format(UI_LANG_ZH),
|
||||
"选项需要参数: %s\n") == 0);
|
||||
assert(strcmp(cli_text_unknown_option_format(UI_LANG_EN),
|
||||
"Unknown option: %s\n") == 0);
|
||||
assert(strcmp(cli_text_unknown_option_format(UI_LANG_ZH),
|
||||
|
|
|
|||
20
tnt.1
20
tnt.1
|
|
@ -26,6 +26,14 @@ tnt \- anonymous SSH chat server with Vim\-style TUI
|
|||
.IR level ]
|
||||
.RB [ \-V | \-\-version ]
|
||||
.RB [ \-h | \-\-help ]
|
||||
.br
|
||||
.B tnt
|
||||
.B \-\-log\-check
|
||||
.I file
|
||||
.br
|
||||
.B tnt
|
||||
.B \-\-log\-recover
|
||||
.I file
|
||||
.SH DESCRIPTION
|
||||
.B tnt
|
||||
is a multi\-user anonymous chat server accessed over SSH.
|
||||
|
|
@ -117,6 +125,18 @@ Overrides the
|
|||
.B TNT_SSH_LOG_LEVEL
|
||||
environment variable.
|
||||
.TP
|
||||
.BR \-\-log\-check " " \fIfile\fR
|
||||
Check a
|
||||
.I messages.log
|
||||
v1 file and print record counts.
|
||||
Exits non-zero when invalid records are found or the file cannot be read.
|
||||
.TP
|
||||
.BR \-\-log\-recover " " \fIfile\fR
|
||||
Write valid
|
||||
.I messages.log
|
||||
v1 records to standard output and print a recovery summary to standard error.
|
||||
The source file is not modified.
|
||||
.TP
|
||||
.BR \-V ", " \-\-version
|
||||
Print version and exit.
|
||||
.TP
|
||||
|
|
|
|||
Loading…
Reference in a new issue