feat: add rsync stderr output modes
CI / lint (pull_request) Successful in 11s
CI / sanitizers (address) (pull_request) Successful in 38s
CI / sanitizers (undefined) (pull_request) Successful in 38s
CI / fuzz-build (pull_request) Successful in 15s
CI / coverage (pull_request) Successful in 31s
CI / build-and-test (pull_request) Successful in 1m15s
CI / valgrind (pull_request) Successful in 33s
CI / lint (pull_request) Successful in 11s
CI / sanitizers (address) (pull_request) Successful in 38s
CI / sanitizers (undefined) (pull_request) Successful in 38s
CI / fuzz-build (pull_request) Successful in 15s
CI / coverage (pull_request) Successful in 31s
CI / build-and-test (pull_request) Successful in 1m15s
CI / valgrind (pull_request) Successful in 33s
This commit is contained in:
+1
-1
@@ -25,7 +25,7 @@ This document maps rsync's full feature set to FastSync's current implementation
|
|||||||
| `-V`, `--version` | Print version | ✅ Implemented | |
|
| `-V`, `--version` | Print version | ✅ Implemented | |
|
||||||
| `--info=FLAGS` | Fine-grained info verbosity | ❌ Not Implemented | Removed because it had no effect |
|
| `--info=FLAGS` | Fine-grained info verbosity | ❌ Not Implemented | Removed because it had no effect |
|
||||||
| `--debug=FLAGS` | Fine-grained debug verbosity | ❌ Not Implemented | Removed because it had no effect |
|
| `--debug=FLAGS` | Fine-grained debug verbosity | ❌ Not Implemented | Removed because it had no effect |
|
||||||
| `--stderr=MODE` | Change stderr output mode | ❌ Not Implemented | |
|
| `--stderr=MODE` | Change stderr output mode | ⚠️ Partial | `errors` (default), `all`, and `client` are accepted; `client` preserves local client routing because FastSync has no rsync message channel |
|
||||||
| `--no-motd` | Suppress daemon MOTD | ❌ Not Implemented | |
|
| `--no-motd` | Suppress daemon MOTD | ❌ Not Implemented | |
|
||||||
| `--exclude=PATTERN` | Exclude files matching pattern | ✅ Implemented | Glob matching in scanner |
|
| `--exclude=PATTERN` | Exclude files matching pattern | ✅ Implemented | Glob matching in scanner |
|
||||||
| `--include=PATTERN` | Include files matching pattern | ✅ Implemented | Glob matching in scanner |
|
| `--include=PATTERN` | Include files matching pattern | ✅ Implemented | Glob matching in scanner |
|
||||||
|
|||||||
@@ -86,6 +86,20 @@ static int set_nonneg_int_option(int* dest, const char* value, const char* optio
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int set_stderr_mode(const char* value) {
|
||||||
|
if (strcmp(value, "errors") == 0 || strcmp(value, "e") == 0)
|
||||||
|
log_set_stderr_mode(LOG_STDERR_ERRORS);
|
||||||
|
else if (strcmp(value, "all") == 0 || strcmp(value, "a") == 0)
|
||||||
|
log_set_stderr_mode(LOG_STDERR_ALL);
|
||||||
|
else if (strcmp(value, "client") == 0 || strcmp(value, "c") == 0)
|
||||||
|
log_set_stderr_mode(LOG_STDERR_CLIENT);
|
||||||
|
else {
|
||||||
|
log_message(LOG_LEVEL_ERROR, "--stderr must be errors, all, or client");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int read_patterns_from_file(const char* filepath, char*** patterns, int* count);
|
static int read_patterns_from_file(const char* filepath, char*** patterns, int* count);
|
||||||
|
|
||||||
/* Parse a string as an unsigned long long. Returns 0 on success, -1 on error. */
|
/* Parse a string as an unsigned long long. Returns 0 on success, -1 on error. */
|
||||||
@@ -341,6 +355,12 @@ int parse_args(Config* config, int argc, char* argv[], int* positional_args,
|
|||||||
}
|
}
|
||||||
config->log_file = lf;
|
config->log_file = lf;
|
||||||
log_set_file(lf);
|
log_set_file(lf);
|
||||||
|
} else if (strncmp(argv[i], "--stderr=", 9) == 0) {
|
||||||
|
if (set_stderr_mode(argv[i] + 9) != 0)
|
||||||
|
return -1;
|
||||||
|
} else if (opt_is(argv[i], "--stderr", NULL)) {
|
||||||
|
if (i + 1 >= argc || set_stderr_mode(argv[++i]) != 0)
|
||||||
|
return -1;
|
||||||
} else if (opt_is(argv[i], "--exclude-from", NULL) && i + 1 < argc) {
|
} else if (opt_is(argv[i], "--exclude-from", NULL) && i + 1 < argc) {
|
||||||
if (read_patterns_from_file(argv[++i], &config->exclude_patterns, &config->exclude_count) !=
|
if (read_patterns_from_file(argv[++i], &config->exclude_patterns, &config->exclude_count) !=
|
||||||
0)
|
0)
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ void print_usage(void) {
|
|||||||
printf(" --stats Print transfer statistics at end\n");
|
printf(" --stats Print transfer statistics at end\n");
|
||||||
printf(" --max-depth <n> Maximum directory depth (0=unlimited)\n");
|
printf(" --max-depth <n> Maximum directory depth (0=unlimited)\n");
|
||||||
printf(" --log-file <path> Write log messages to file\n");
|
printf(" --log-file <path> Write log messages to file\n");
|
||||||
|
printf(" --stderr=MODE Route logging to stderr: errors, all, or client\n");
|
||||||
printf(" --partial Keep partial files on interrupted transfer\n");
|
printf(" --partial Keep partial files on interrupted transfer\n");
|
||||||
printf(" --partial-dir <dir> Directory for partial files\n");
|
printf(" --partial-dir <dir> Directory for partial files\n");
|
||||||
printf(" --fastsync-server-path <path>\n");
|
printf(" --fastsync-server-path <path>\n");
|
||||||
|
|||||||
+10
-1
@@ -8,6 +8,7 @@
|
|||||||
static const char* log_level_strings[] = {"DEBUG", "INFO", "WARN", "ERROR"};
|
static const char* log_level_strings[] = {"DEBUG", "INFO", "WARN", "ERROR"};
|
||||||
static LogLevel current_log_level = LOG_LEVEL_WARNING;
|
static LogLevel current_log_level = LOG_LEVEL_WARNING;
|
||||||
static FILE* log_fp = NULL;
|
static FILE* log_fp = NULL;
|
||||||
|
static LogStderrMode stderr_mode = LOG_STDERR_ERRORS;
|
||||||
|
|
||||||
void set_log_level(LogLevel level) {
|
void set_log_level(LogLevel level) {
|
||||||
current_log_level = level;
|
current_log_level = level;
|
||||||
@@ -17,6 +18,14 @@ void log_set_file(FILE* fp) {
|
|||||||
log_fp = fp;
|
log_fp = fp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void log_set_stderr_mode(LogStderrMode mode) {
|
||||||
|
stderr_mode = mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
LogStderrMode log_get_stderr_mode(void) {
|
||||||
|
return stderr_mode;
|
||||||
|
}
|
||||||
|
|
||||||
static inline void write_message(FILE* dest_io, LogLevel log_level, struct tm t, const char* format,
|
static inline void write_message(FILE* dest_io, LogLevel log_level, struct tm t, const char* format,
|
||||||
va_list args) {
|
va_list args) {
|
||||||
fprintf(dest_io, "%04d-%02d-%02d %02d:%02d:%02d [%s]: ", t.tm_year + 1900, t.tm_mon + 1,
|
fprintf(dest_io, "%04d-%02d-%02d %02d:%02d:%02d [%s]: ", t.tm_year + 1900, t.tm_mon + 1,
|
||||||
@@ -37,7 +46,7 @@ void log_message(LogLevel log_level, const char* format, ...) {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
FILE* dest_io = stdout;
|
FILE* dest_io = stdout;
|
||||||
if (log_level == LOG_LEVEL_ERROR) {
|
if (stderr_mode == LOG_STDERR_ALL || log_level == LOG_LEVEL_ERROR) {
|
||||||
dest_io = stderr;
|
dest_io = stderr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,10 +4,13 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
typedef enum { LOG_LEVEL_DEBUG, LOG_LEVEL_INFO, LOG_LEVEL_WARNING, LOG_LEVEL_ERROR } LogLevel;
|
typedef enum { LOG_LEVEL_DEBUG, LOG_LEVEL_INFO, LOG_LEVEL_WARNING, LOG_LEVEL_ERROR } LogLevel;
|
||||||
|
typedef enum { LOG_STDERR_ERRORS, LOG_STDERR_ALL, LOG_STDERR_CLIENT } LogStderrMode;
|
||||||
|
|
||||||
void log_message(LogLevel log_level, const char* message, ...);
|
void log_message(LogLevel log_level, const char* message, ...);
|
||||||
void log_perror(const char* context);
|
void log_perror(const char* context);
|
||||||
void set_log_level(LogLevel level);
|
void set_log_level(LogLevel level);
|
||||||
void log_set_file(FILE* fp);
|
void log_set_file(FILE* fp);
|
||||||
|
void log_set_stderr_mode(LogStderrMode mode);
|
||||||
|
LogStderrMode log_get_stderr_mode(void);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "test_client_cli.h"
|
#include "test_client_cli.h"
|
||||||
#include "client_validation.h"
|
#include "client_validation.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
#include "log.h"
|
||||||
#include "test_utils.h"
|
#include "test_utils.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
@@ -339,6 +340,34 @@ static void test_parse_args_archive() {
|
|||||||
config_delete(cfg);
|
config_delete(cfg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void test_parse_args_stderr_modes() {
|
||||||
|
static const char* const modes[] = {"errors", "all", "client", "e", "a", "c"};
|
||||||
|
static const LogStderrMode expected[] = {LOG_STDERR_ERRORS, LOG_STDERR_ALL, LOG_STDERR_CLIENT,
|
||||||
|
LOG_STDERR_ERRORS, LOG_STDERR_ALL, LOG_STDERR_CLIENT};
|
||||||
|
for (size_t i = 0; i < sizeof(modes) / sizeof(modes[0]); i++) {
|
||||||
|
Config* cfg = config_create();
|
||||||
|
char option[32];
|
||||||
|
snprintf(option, sizeof(option), "--stderr=%s", modes[i]);
|
||||||
|
char* argv[] = {"fastsync", option, "/src", "/dst"};
|
||||||
|
int positional_args[2];
|
||||||
|
int positional_count = 0;
|
||||||
|
EXPECT_EQ_INT(parse_args(cfg, 4, argv, positional_args, &positional_count), 0);
|
||||||
|
EXPECT_EQ_INT(log_get_stderr_mode(), expected[i]);
|
||||||
|
config_delete(cfg);
|
||||||
|
}
|
||||||
|
log_set_stderr_mode(LOG_STDERR_ERRORS);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_parse_args_rejects_invalid_stderr_mode() {
|
||||||
|
Config* cfg = config_create();
|
||||||
|
char* argv[] = {"fastsync", "--stderr=invalid", "/src", "/dst"};
|
||||||
|
int positional_args[2];
|
||||||
|
int positional_count = 0;
|
||||||
|
EXPECT_EQ_INT(parse_args(cfg, 4, argv, positional_args, &positional_count), -1);
|
||||||
|
config_delete(cfg);
|
||||||
|
log_set_stderr_mode(LOG_STDERR_ERRORS);
|
||||||
|
}
|
||||||
|
|
||||||
void test_client_cli() {
|
void test_client_cli() {
|
||||||
test_validate_config_required_paths();
|
test_validate_config_required_paths();
|
||||||
test_validate_config_incompatible_options();
|
test_validate_config_incompatible_options();
|
||||||
@@ -360,4 +389,6 @@ void test_client_cli() {
|
|||||||
test_parse_args_unknown_option();
|
test_parse_args_unknown_option();
|
||||||
test_parse_args_rejects_unimplemented_options();
|
test_parse_args_rejects_unimplemented_options();
|
||||||
test_parse_args_archive();
|
test_parse_args_archive();
|
||||||
|
test_parse_args_stderr_modes();
|
||||||
|
test_parse_args_rejects_invalid_stderr_mode();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
#include "test_log.h"
|
#include "test_log.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "test_utils.h"
|
#include "test_utils.h"
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
/* Test default log level: WARNING and ERROR should print, DEBUG and INFO should not.
|
/* Test default log level: WARNING and ERROR should print, DEBUG and INFO should not.
|
||||||
* We can't easily capture stderr in unit tests, so we verify the functions don't crash
|
* We can't easily capture stderr in unit tests, so we verify the functions don't crash
|
||||||
@@ -90,6 +92,29 @@ static void test_log_filtering() {
|
|||||||
EXPECT_TRUE(true);
|
EXPECT_TRUE(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void test_log_stderr_mode_all() {
|
||||||
|
int pipe_fds[2];
|
||||||
|
EXPECT_EQ_INT(pipe(pipe_fds), 0);
|
||||||
|
int saved_stderr = dup(STDERR_FILENO);
|
||||||
|
EXPECT_TRUE(saved_stderr >= 0);
|
||||||
|
EXPECT_TRUE(dup2(pipe_fds[1], STDERR_FILENO) >= 0);
|
||||||
|
close(pipe_fds[1]);
|
||||||
|
|
||||||
|
set_log_level(LOG_LEVEL_WARNING);
|
||||||
|
log_set_stderr_mode(LOG_STDERR_ALL);
|
||||||
|
log_message(LOG_LEVEL_WARNING, "warning routed to stderr");
|
||||||
|
fflush(stderr);
|
||||||
|
|
||||||
|
EXPECT_TRUE(dup2(saved_stderr, STDERR_FILENO) >= 0);
|
||||||
|
close(saved_stderr);
|
||||||
|
char output[128] = {0};
|
||||||
|
ssize_t length = read(pipe_fds[0], output, sizeof(output) - 1);
|
||||||
|
close(pipe_fds[0]);
|
||||||
|
EXPECT_TRUE(length > 0);
|
||||||
|
EXPECT_TRUE(strstr(output, "warning routed to stderr") != NULL);
|
||||||
|
log_set_stderr_mode(LOG_STDERR_ERRORS);
|
||||||
|
}
|
||||||
|
|
||||||
/* Test that log_message handles various format strings */
|
/* Test that log_message handles various format strings */
|
||||||
static void test_log_message_formats() {
|
static void test_log_message_formats() {
|
||||||
set_log_level(LOG_LEVEL_DEBUG);
|
set_log_level(LOG_LEVEL_DEBUG);
|
||||||
@@ -112,5 +137,6 @@ void test_log() {
|
|||||||
test_log_set_level_info();
|
test_log_set_level_info();
|
||||||
test_log_set_level_error();
|
test_log_set_level_error();
|
||||||
test_log_filtering();
|
test_log_filtering();
|
||||||
|
test_log_stderr_mode_all();
|
||||||
test_log_message_formats();
|
test_log_message_formats();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user