349 lines
9.3 KiB
C
349 lines
9.3 KiB
C
#include <pthread.h>
|
|
#include <signal.h>
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <strings.h>
|
|
#include <time.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
|
|
#include "pdbg.h"
|
|
#include "pmem.h"
|
|
#include "ppath.h"
|
|
#include "putil.h"
|
|
|
|
extern PSYNC_THREAD const char *psync_thread_name;
|
|
|
|
static FILE *log_file = NULL;
|
|
static FILE *fs_event_log = NULL;
|
|
static int fs_event_log_initialized = 0;
|
|
static pthread_mutex_t log_mutex = PTHREAD_MUTEX_INITIALIZER;
|
|
static volatile sig_atomic_t log_reopen_requested = 0;
|
|
|
|
/* Runtime debug level - defaults to D_INFO */
|
|
unsigned int pdbg_runtime_level = D_INFO;
|
|
static int pdbg_level_initialized = 0;
|
|
|
|
/* Forward declaration */
|
|
static void do_reopen_log(void);
|
|
|
|
/* Initialize debug level from environment variable */
|
|
static void pdbg_init_level(void) {
|
|
if (pdbg_level_initialized)
|
|
return;
|
|
|
|
pdbg_level_initialized = 1;
|
|
|
|
const char *level_str = getenv("PCLOUD_LOG_LEVEL");
|
|
if (!level_str || level_str[0] == '\0') {
|
|
__atomic_store_n(&pdbg_runtime_level, D_INFO, __ATOMIC_RELAXED);
|
|
return;
|
|
}
|
|
|
|
unsigned int level;
|
|
/* Parse log level string (case-insensitive) */
|
|
if (strcasecmp(level_str, "NONE") == 0) {
|
|
level = D_NONE;
|
|
} else if (strcasecmp(level_str, "BUG") == 0 || strcasecmp(level_str, "DEBUG") == 0) {
|
|
level = D_BUG;
|
|
} else if (strcasecmp(level_str, "CRITICAL") == 0) {
|
|
level = D_CRITICAL;
|
|
} else if (strcasecmp(level_str, "ERROR") == 0) {
|
|
level = D_ERROR;
|
|
} else if (strcasecmp(level_str, "WARNING") == 0 || strcasecmp(level_str, "WARN") == 0) {
|
|
level = D_WARNING;
|
|
} else if (strcasecmp(level_str, "INFO") == 0) {
|
|
level = D_INFO;
|
|
} else if (strcasecmp(level_str, "NOTICE") == 0) {
|
|
level = D_NOTICE;
|
|
} else {
|
|
/* Invalid level, default to INFO */
|
|
level = D_INFO;
|
|
}
|
|
__atomic_store_n(&pdbg_runtime_level, level, __ATOMIC_RELAXED);
|
|
}
|
|
|
|
/* Returns 1 if path is safe to use as a log file, 0 otherwise.
|
|
* Safe means: absolute, no '..' components, and under HOME or /tmp. */
|
|
static int pdbg_path_is_safe(const char *path) {
|
|
const char *home;
|
|
const char *p;
|
|
|
|
/* Must be an absolute path */
|
|
if (!path || path[0] != '/')
|
|
return 0;
|
|
|
|
/* Reject any '..' path component to prevent directory traversal */
|
|
p = path;
|
|
while (*p) {
|
|
while (*p == '/') p++;
|
|
if (p[0] == '.' && p[1] == '.' && (p[2] == '/' || p[2] == '\0'))
|
|
return 0;
|
|
while (*p && *p != '/') p++;
|
|
}
|
|
|
|
/* Must resolve within the user home directory */
|
|
home = getenv("HOME");
|
|
if (home && home[0] == '/') {
|
|
size_t hlen = strlen(home);
|
|
if (strncmp(path, home, hlen) == 0 &&
|
|
(path[hlen] == '/' || path[hlen] == '\0'))
|
|
return 1;
|
|
}
|
|
|
|
/* Or within /tmp */
|
|
if (strncmp(path, "/tmp/", 5) == 0)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
char *psync_debug_path() {
|
|
const char *custom_path = getenv("PCLOUD_LOG_PATH");
|
|
if (custom_path && custom_path[0] != '\0') {
|
|
if (!pdbg_path_is_safe(custom_path)) {
|
|
/* Invalid path: reject and fall through to default */
|
|
fprintf(stderr,
|
|
"pdbg: PCLOUD_LOG_PATH '%s' rejected"
|
|
" (must be absolute, under HOME or /tmp, no '..' components);"
|
|
" using default log path\n",
|
|
custom_path);
|
|
} else {
|
|
size_t len = strlen(custom_path) + 1;
|
|
char *path = (char *)pmem_malloc(PMEM_SUBSYS_OTHER, len);
|
|
if (!path) {
|
|
return NULL;
|
|
}
|
|
snprintf(path, len, "%s", custom_path);
|
|
return path;
|
|
}
|
|
}
|
|
|
|
char *home = ppath_home();
|
|
if (!home) {
|
|
return NULL;
|
|
}
|
|
|
|
const char *subdir = "/.pcloud/debug.log";
|
|
size_t len = strlen(home) + strlen(subdir) + 1;
|
|
char *sockpath = (char *)pmem_malloc(PMEM_SUBSYS_OTHER, len);
|
|
if (!sockpath) {
|
|
pmem_free(PMEM_SUBSYS_OTHER, home);
|
|
return NULL;
|
|
}
|
|
|
|
snprintf(sockpath, len, "%s%s", home, subdir);
|
|
pmem_free(PMEM_SUBSYS_OTHER, home);
|
|
return sockpath;
|
|
}
|
|
|
|
char *pfs_event_log_path() {
|
|
const char *custom_path = getenv("PCLOUD_FS_EVENT_LOG");
|
|
if (custom_path && custom_path[0] != '\0') {
|
|
size_t len = strlen(custom_path) + 1;
|
|
char *path = (char *)pmem_malloc(PMEM_SUBSYS_OTHER, len);
|
|
if (!path) {
|
|
return NULL;
|
|
}
|
|
snprintf(path, len, "%s", custom_path);
|
|
return path;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
int pdbg_printf(const char *file, const char *function, int unsigned line, int unsigned level, const char *fmt, ...) {
|
|
/* Recursion guard */
|
|
static __thread int in_pdbg_printf = 0;
|
|
if (in_pdbg_printf)
|
|
return 1;
|
|
in_pdbg_printf = 1;
|
|
|
|
/* Initialize debug level from environment on first call */
|
|
pdbg_init_level();
|
|
|
|
if (!IS_DEBUG) {
|
|
in_pdbg_printf = 0;
|
|
return 1;
|
|
}
|
|
|
|
static const struct {
|
|
unsigned long level;
|
|
const char *name;
|
|
} debug_levels[] = DEBUG_LEVELS;
|
|
struct timespec ts;
|
|
char dttime[36], format[512];
|
|
va_list ap;
|
|
const char *errname;
|
|
unsigned long i;
|
|
unsigned int u;
|
|
pthread_t threadid;
|
|
errname = "BAD_ERROR_CODE";
|
|
for (i = 0; i < ARRAY_SIZE(debug_levels); i++)
|
|
if (debug_levels[i].level == level) {
|
|
errname = debug_levels[i].name;
|
|
break;
|
|
}
|
|
|
|
pthread_mutex_lock(&log_mutex);
|
|
|
|
/* Check if log rotation was requested via signal */
|
|
if (unlikely(log_reopen_requested)) {
|
|
log_reopen_requested = 0;
|
|
do_reopen_log();
|
|
}
|
|
|
|
if (unlikely(!log_file)) {
|
|
char *path = psync_debug_path();
|
|
int fd = open(path, O_WRONLY | O_CREAT | O_APPEND, 0600);
|
|
if (fd == -1) {
|
|
pmem_free(PMEM_SUBSYS_OTHER, path);
|
|
pthread_mutex_unlock(&log_mutex);
|
|
return 1;
|
|
}
|
|
log_file = fdopen(fd, "a");
|
|
pmem_free(PMEM_SUBSYS_OTHER, path);
|
|
if (!log_file) {
|
|
close(fd);
|
|
pthread_mutex_unlock(&log_mutex);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
/* Create empty fs event log file on first call if configured */
|
|
if (unlikely(!fs_event_log_initialized)) {
|
|
fs_event_log_initialized = 1;
|
|
char *path = pfs_event_log_path();
|
|
if (path) {
|
|
/* Create empty file if it doesn't exist, or open existing */
|
|
int fd = open(path, O_WRONLY | O_CREAT | O_APPEND, 0600);
|
|
if (fd != -1) {
|
|
close(fd);
|
|
}
|
|
pmem_free(PMEM_SUBSYS_OTHER, path);
|
|
}
|
|
}
|
|
|
|
clock_gettime(CLOCK_REALTIME, &ts);
|
|
putil_time_format(ts.tv_sec, ts.tv_nsec, dttime);
|
|
threadid = pthread_self();
|
|
memcpy(&u, &threadid, sizeof(u));
|
|
snprintf(format, sizeof(format), "%s %u %s %s: %s:%u (function %s): %s\n",
|
|
dttime, u, psync_thread_name, errname, file, line, function, fmt);
|
|
format[sizeof(format) - 1] = 0;
|
|
va_start(ap, fmt);
|
|
vfprintf(log_file, format, ap);
|
|
va_end(ap);
|
|
fflush(log_file);
|
|
pthread_mutex_unlock(&log_mutex);
|
|
in_pdbg_printf = 0;
|
|
return 1;
|
|
}
|
|
|
|
/* Called from signal handler - must be signal-safe (no malloc/file ops) */
|
|
void pdbg_reopen_log() {
|
|
log_reopen_requested = 1;
|
|
}
|
|
|
|
/* Internal function to actually reopen the log - called from safe context */
|
|
static void do_reopen_log() {
|
|
char *path;
|
|
FILE *new_log;
|
|
int fd;
|
|
|
|
/* Close existing log file if open */
|
|
if (log_file) {
|
|
fclose(log_file);
|
|
log_file = NULL;
|
|
}
|
|
|
|
/* Open new log file */
|
|
path = psync_debug_path();
|
|
if (path) {
|
|
fd = open(path, O_WRONLY | O_CREAT | O_APPEND, 0600);
|
|
pmem_free(PMEM_SUBSYS_OTHER, path);
|
|
if (fd != -1) {
|
|
new_log = fdopen(fd, "a");
|
|
if (new_log) {
|
|
log_file = new_log;
|
|
/* Log a message about the rotation */
|
|
fprintf(log_file, "Log file reopened for rotation\n");
|
|
fflush(log_file);
|
|
} else {
|
|
close(fd);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Also reopen fs event log if it was open */
|
|
if (fs_event_log) {
|
|
fclose(fs_event_log);
|
|
fs_event_log = NULL;
|
|
|
|
path = pfs_event_log_path();
|
|
if (path) {
|
|
fd = open(path, O_WRONLY | O_CREAT | O_APPEND, 0600);
|
|
pmem_free(PMEM_SUBSYS_OTHER, path);
|
|
if (fd != -1) {
|
|
new_log = fdopen(fd, "a");
|
|
if (new_log) {
|
|
fs_event_log = new_log;
|
|
} else {
|
|
close(fd);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Write a filesystem event to the fs-event log with timestamp */
|
|
void pdbg_write_fs_event(const char *fmt, ...) {
|
|
struct timespec ts;
|
|
char dttime[36];
|
|
va_list ap;
|
|
|
|
pthread_mutex_lock(&log_mutex);
|
|
|
|
/* Check if log rotation was requested */
|
|
if (unlikely(log_reopen_requested)) {
|
|
log_reopen_requested = 0;
|
|
do_reopen_log();
|
|
}
|
|
|
|
/* Open fs event log if not already open */
|
|
if (unlikely(!fs_event_log)) {
|
|
char *path = pfs_event_log_path();
|
|
if (!path) {
|
|
/* FS event log not configured, skip */
|
|
pthread_mutex_unlock(&log_mutex);
|
|
return;
|
|
}
|
|
int fd = open(path, O_WRONLY | O_CREAT | O_APPEND, 0600);
|
|
pmem_free(PMEM_SUBSYS_OTHER, path);
|
|
if (fd == -1) {
|
|
pthread_mutex_unlock(&log_mutex);
|
|
return;
|
|
}
|
|
fs_event_log = fdopen(fd, "a");
|
|
if (!fs_event_log) {
|
|
close(fd);
|
|
pthread_mutex_unlock(&log_mutex);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Get timestamp */
|
|
clock_gettime(CLOCK_REALTIME, &ts);
|
|
putil_time_format(ts.tv_sec, ts.tv_nsec, dttime);
|
|
|
|
/* Write timestamp followed by event message */
|
|
fprintf(fs_event_log, "%s ", dttime);
|
|
va_start(ap, fmt);
|
|
vfprintf(fs_event_log, fmt, ap);
|
|
va_end(ap);
|
|
fprintf(fs_event_log, "\n");
|
|
fflush(fs_event_log);
|
|
|
|
pthread_mutex_unlock(&log_mutex);
|
|
} |