From 441a0d69eddb49751be9ee066755f39b20f5a844 Mon Sep 17 00:00:00 2001 From: Jonathan Senkerik Date: Fri, 27 Feb 2026 02:38:28 -0500 Subject: [PATCH] Refactor logging, add cache-size, fs-event-log (#163) Much appreciated! Merged. :) --- LOG-MANAGEMENT.md | 134 +++++++++++++++++++++++++++ Makefile | 17 +++- README.md | 4 + doc/USAGE.md | 165 ++++++++++++++++++++++++++++++++- main.cpp | 79 +++++++++++++++- pcloudcc.logrotate | 47 ++++++++++ pclsync/pdbg.c | 213 +++++++++++++++++++++++++++++++++++++++++-- pclsync/pdbg.h | 19 ++-- pclsync/pdiff.c | 138 +++++++++++++++++++++++++++- pclsync/pdownload.c | 1 + pclsync/pfs.c | 102 ++++++++++++++++++++- pclsync/ppagecache.c | 26 +++--- pclsync/psql.c | 32 ++++++- pclsync/pssl.c | 27 +++--- pclsync/psynclib.h | 1 + pclsync/putil.c | 6 +- pclsync_lib.cpp | 60 +++++++++++- pclsync_lib.h | 1 + 18 files changed, 1005 insertions(+), 67 deletions(-) create mode 100644 LOG-MANAGEMENT.md create mode 100644 pcloudcc.logrotate diff --git a/LOG-MANAGEMENT.md b/LOG-MANAGEMENT.md new file mode 100644 index 0000000..9cd8798 --- /dev/null +++ b/LOG-MANAGEMENT.md @@ -0,0 +1,134 @@ +# Log Management Guide + +This guide covers log configuration and rotation for `pcloudcc`. + +## Log Files + +`pcloudcc` supports two types of logs: + +1. **Debug Log** - Operational information, errors, and debug messages + - Default location: `~/.pcloud/debug.log` + - Configure with: `--log-path` and `--log-level` + +2. **Filesystem Events Log** - Clean log of file/folder operations only + - Disabled by default + - Enable with: `--fs-event-log` + +## Log Configuration + +### Log Level + +Control verbosity with `--log-level`: + +```bash +# Errors only +pcloudcc -u user@example.com -d --log-level ERROR + +# Normal operation (default) +pcloudcc -u user@example.com -d --log-level INFO + +# Detailed debugging +pcloudcc -u user@example.com -d --log-level DEBUG + +# Disable logging +pcloudcc -u user@example.com -d --log-level NONE +``` + +Available levels: `NONE`, `ERROR`, `WARNING`, `INFO`, `NOTICE`, `DEBUG` + +### Custom Log Path + +```bash +# System-wide log +pcloudcc -u user@example.com -d --log-path /var/log/pcloudcc.log + +# Custom user location +pcloudcc -u user@example.com -d --log-path ~/logs/pcloud.log +``` + +### Filesystem Events Log + +Enable a separate log containing only filesystem events: + +```bash +pcloudcc -u user@example.com -d --fs-event-log ~/.pcloud/fs-events.log +``` + +Event format: ` ` + +Example events: +``` +Wed, 12 Nov 2025 05:24:23.032 +0000 file deleted /tmp/xxx +Wed, 12 Nov 2025 05:24:47.861 +0000 folder moved /tmp/test2 -> /tmp/test3 +Wed, 12 Nov 2025 05:25:10.445 +0000 file created /tmp/newfile.txt +``` + +## Log Rotation + +### System-Wide (with logrotate) + +Install the provided configuration: + +```bash +sudo cp pcloudcc.logrotate /etc/logrotate.d/pcloudcc +``` + +This rotates logs daily, keeps 7 days of history, and compresses old logs. + +### User-Specific Rotation + +Create `~/.logrotate.conf`: + +``` +/home/username/.pcloud/debug.log /home/username/.pcloud/fs-events.log { + weekly + rotate 4 + compress + missingok + notifempty + create 0600 username username + maxsize 50M + postrotate + pkill -USR2 -u username pcloudcc 2>/dev/null || true + endscript +} +``` + +Add to crontab: + +```bash +crontab -e +# Add this line: +0 2 * * * /usr/sbin/logrotate -s ~/.logrotate.state ~/.logrotate.conf +``` + +### Manual Rotation + +```bash +# Move current logs +mv ~/.pcloud/debug.log ~/.pcloud/debug.log.old +mv ~/.pcloud/fs-events.log ~/.pcloud/fs-events.log.old + +# Signal pcloudcc to reopen logs +pkill -SIGUSR2 pcloudcc +``` + +## Cache Management + +Configure cache size (default 5GB): + +```bash +# Set to 10GB +pcloudcc -u user@example.com -d --cache-size 10 + +# Set to 1GB for limited storage +pcloudcc -u user@example.com -d --cache-size 1 +``` + +Check cache usage: + +```bash +du -sh ~/.pcloud/Cache +``` + +Cache is stored in `~/.pcloud/Cache/` and managed automatically by `pcloudcc`. diff --git a/Makefile b/Makefile index fb7817a..3c9cbd8 100644 --- a/Makefile +++ b/Makefile @@ -25,6 +25,7 @@ CPPOBJ := $(notdir $(CPPSRC:%.cpp=%.o)) DESTDIR := /usr/local STATIC := 1 +FULLSTATIC := 0 BUILD := release SSLDBGLVL := 0 @@ -45,6 +46,12 @@ else $(error Invalid BUILD. Use 'debug' or 'release') endif +ifeq ($(FULLSTATIC), 1) + # Force static linking of all libraries + LIBLDFLAGS += -static + EXECLDFLAGS += -static +endif + CFLAGS += $(DIST_CFLAGS) CXXFLAGS += $(DIST_CXXFLAGS) LIBLDFLAGS += $(LDFLAGS) @@ -63,7 +70,7 @@ else EXECLDFLAGS += $(LIBLDFLAGS) endif -.PHONY: all clean install uninstall +.PHONY: all clean install install-logrotate uninstall all: $(TARGETS) @@ -88,6 +95,14 @@ ifeq ($(STATIC), 0) install -m 755 libpcloudcc_lib.so $(DESTDIR)/lib/libpcloudcc_lib.so endif +install-logrotate: + @echo "Installing logrotate configuration (requires root)..." + install -D -m 644 pcloudcc.logrotate /etc/logrotate.d/pcloudcc + @echo "Logrotate configuration installed to /etc/logrotate.d/pcloudcc" + @echo "Note: This handles system-wide logs at /var/log/pcloudcc.log" + @echo "For user-specific logs, see comments in pcloudcc.logrotate" + uninstall: rm -f $(DESTDIR)/bin/pcloudcc rm -f $(DESTDIR)/lib/libpcloudcc_lib.so + rm -f /etc/logrotate.d/pcloudcc diff --git a/README.md b/README.md index b772433..2b43391 100644 --- a/README.md +++ b/README.md @@ -39,3 +39,7 @@ file. - [Usage Guide](./doc/USAGE.md): Read this if you're unsure how to start using `pcloudcc`. + +- [Log Management Guide](./LOG-MANAGEMENT.md): Read this to configure log rotation, + cache size limits, and custom log paths. To set up automatic log rotation, copy + `pcloudcc.logrotate` to `/etc/logrotate.d/pcloudcc`. diff --git a/doc/USAGE.md b/doc/USAGE.md index 56fd899..fbff814 100644 --- a/doc/USAGE.md +++ b/doc/USAGE.md @@ -4,7 +4,109 @@ Terminal command is pcloudcc and -h option prints short options description. -> pcloudcc -h +```bash +pcloudcc -h +``` + +## New Options + +### Cache Size Management + +By default, the cache is limited to 5GB. You can adjust this with `--cache-size` (value in GB): + +```bash +# Set cache to 10GB +pcloudcc -u example@myemail.com -d --cache-size 10 + +# Set to 1GB for limited storage systems +pcloudcc -u example@myemail.com -d --cache-size 1 +``` + +### Custom Log Path + +By default, logs are written to `~/.pcloud/debug.log`. You can specify a custom path: + +```bash +# System-wide log (requires permissions) +pcloudcc -u example@myemail.com -d --log-path /var/log/pcloudcc.log + +# Custom user log +pcloudcc -u example@myemail.com -d --log-path ~/my-pcloud.log +``` + +See [LOG-MANAGEMENT.md](../LOG-MANAGEMENT.md) for log rotation configuration. + +### Log Level Control + +By default, the logging level is set to INFO. You can adjust verbosity with `--log-level`: + +```bash +# Minimal logging (errors only) +pcloudcc -u example@myemail.com -d --log-level ERROR + +# Quiet mode (no logging) +pcloudcc -u example@myemail.com -d --log-level NONE + +# Detailed logging for troubleshooting +pcloudcc -u example@myemail.com -d --log-level DEBUG +``` + +Available levels (from least to most verbose): +- `NONE` - Disable all logging +- `ERROR` - Only critical errors +- `WARNING` - Errors and warnings +- `INFO` - Normal operation info (default) +- `NOTICE` - Informational notices +- `DEBUG` - Detailed debug information + +### Filesystem Events Log + +Enable a separate log containing only filesystem events (no debug messages): + +```bash +pcloudcc -u example@myemail.com -d --fs-event-log ~/.pcloud/fs-events.log +``` + +**Event format:** ` ` + +**Event types:** +- `file created`, `file modified`, `file deleted`, `file downloaded` +- `file moved -> ` +- `folder created`, `folder deleted` +- `folder moved -> ` + +**Example log entries:** +``` +Wed, 12 Nov 2025 05:24:23.032 +0000 file deleted /tmp/xxx +Wed, 12 Nov 2025 05:24:47.861 +0000 folder moved /tmp/test2 -> /tmp/test3 +Wed, 12 Nov 2025 05:25:10.445 +0000 file created /tmp/newfile.txt +``` + +See [LOG-MANAGEMENT.md](../LOG-MANAGEMENT.md) for monitoring scripts and log rotation setup. + +### FUSE Mount Options + +Pass custom FUSE mount options with `--fuse-opts`: + +```bash +# Allow other users to access the mount +pcloudcc -u example@myemail.com -d --fuse-opts allow_other + +# Multiple options (comma-separated) +pcloudcc -u example@myemail.com -d --fuse-opts allow_other,allow_root + +# Set custom permissions +pcloudcc -u example@myemail.com -d --fuse-opts default_permissions,uid=1000,gid=1000 +``` + +**Common FUSE options:** +- `allow_other` - Allow all users to access files (requires `/etc/fuse.conf` setting) +- `allow_root` - Allow root to access files +- `default_permissions` - Enable kernel permission checking +- `uid=N`, `gid=N` - Set file owner/group +- `umask=OCTAL` - Set file permission mask + +See `man fuse` for complete list of available options. ## First Use @@ -28,11 +130,64 @@ Notice that a new user may take a while to mount. Please, be patient. ## Run as Daemon -If you have saved your password, then you can run pcloudcc as a -background daemon. Verify the filesystem is mounted when the daemon -starts. +**Daemon mode (`-d`) is the recommended way to run pcloudcc in production.** Running in foreground mode can cause FUSE filesystem errors and crashes if interrupted. -> pcloudcc -u example@myemail.com -d +If you have saved your password, then you can run pcloudcc as a background daemon: + +```bash +pcloudcc -u example@myemail.com -d +``` + +With custom mount point, cache size, and log path: + +```bash +pcloudcc -u example@myemail.com -d -m /mnt/pcloud --cache-size 10 --log-path /var/log/pcloudcc.log +``` + +Verify the filesystem is mounted when the daemon starts: + +```bash +mount | grep pCloud +ps aux | grep pcloudcc +``` + +### Stopping the Daemon + +To stop the daemon gracefully, use one of these methods: + +```bash +# Graceful shutdown (recommended) +pkill -TERM pcloudcc + +# Or use the command prompt +pcloudcc -k +# Then type: finalize +``` + +**Important**: Do NOT use `kill -9` or interrupt with Ctrl+C, as this can cause filesystem corruption. + +### Available Signals + +- **SIGTERM / SIGINT**: Gracefully shut down the daemon +- **SIGHUP**: Terminate the daemon (exits the process) +- **SIGUSR1**: Dump internal debugging information (debug builds only) +- **SIGUSR2**: Reopen log files for rotation + +#### Log Rotation with SIGUSR2 + +The daemon responds to SIGUSR2 by reopening both the debug log and fs-events log (if enabled). This allows safe log rotation: + +```bash +# Manual log rotation +mv ~/.pcloud/debug.log ~/.pcloud/debug.log.old +pkill -SIGUSR2 pcloudcc + +# With fs-events log +mv ~/.pcloud/fs-events.log ~/.pcloud/fs-events.log.old +pkill -SIGUSR2 pcloudcc +``` + +For automatic log rotation with logrotate, see [LOG-MANAGEMENT.md](../LOG-MANAGEMENT.md). ## Command Prompt diff --git a/main.cpp b/main.cpp index b81a0e6..722fe66 100644 --- a/main.cpp +++ b/main.cpp @@ -25,6 +25,7 @@ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#include #include #include @@ -33,6 +34,7 @@ #include "control_tools.h" #include "pclsync_lib.h" +#include "pclsync/psettings.h" namespace po = boost::program_options; namespace ct = control_tools; @@ -71,7 +73,12 @@ int main(int argc, char **argv) { ("mountpoint,m", po::value(), "Specify where pCloud filesystem is mounted.") ("commands_only,k", po::bool_switch(&commands_only), "Open command prompt to interact with running daemon.") ("newuser,n", po::bool_switch(&newuser), "Register a new pCloud user account.") - ("savepassword,s", po::bool_switch(&save_pass), "Save user password in the database."); + ("savepassword,s", po::bool_switch(&save_pass), "Save user password in the database.") + ("cache-size", po::value(), "Maximum cache size in GB (default: 5GB).") + ("log-path", po::value(), "Custom path for debug.log (default: ~/.pcloud/debug.log).") + ("log-level", po::value(), "Logging level: NONE, ERROR, WARNING, INFO (default), NOTICE, DEBUG.") + ("fs-event-log", po::value(), "Path to filesystem events log (default: disabled).") + ("fuse-opts,O", po::value(), "FUSE mount options (e.g., 'allow_other,allow_root')."); po::command_line_parser parser{argc, argv}; po::positional_options_description p; @@ -100,9 +107,16 @@ int main(int argc, char **argv) { } - if ((!vm.count("username"))) { + // Environment variable fallbacks + if (!vm.count("username")) { + const char *env_user = std::getenv("PCLOUD_USER"); + if (env_user && env_user[0]) + username = env_user; + } + + if (!vm.count("username") && username.empty()) { std::cout << "Username option is required, specify with " - << "-u or --username." << std::endl; + << "-u or --username, or set PCLOUD_USER." << std::endl; return 1; } @@ -118,6 +132,11 @@ int main(int argc, char **argv) { cc::clibrary::pclsync_lib::get_lib().set_username(username); if (passwordsw) { cc::clibrary::pclsync_lib::get_lib().read_password(); + } else { + const char *env_pass = std::getenv("PCLOUD_ACCOUNT_PASSWORD"); + if (env_pass && env_pass[0]) { + cc::clibrary::pclsync_lib::get_lib().set_password(std::string(env_pass)); + } } cc::clibrary::pclsync_lib::get_lib().set_tfa_code(tfa_code); cc::clibrary::pclsync_lib::get_lib().set_trusted_device(trusted_device); @@ -126,8 +145,13 @@ int main(int argc, char **argv) { if (vm.count("passascrypto")) { cc::clibrary::pclsync_lib::get_lib().set_crypto_pass(password); } else { - std::cout << "Enter crypto password." << std::endl; - cc::clibrary::pclsync_lib::get_lib().read_cryptopass(); + const char *env_crypto = std::getenv("PCLOUD_CRYPTO_PASSWORD"); + if (env_crypto && env_crypto[0]) { + cc::clibrary::pclsync_lib::get_lib().set_crypto_pass(std::string(env_crypto)); + } else { + std::cout << "Enter crypto password." << std::endl; + cc::clibrary::pclsync_lib::get_lib().read_cryptopass(); + } } } else cc::clibrary::pclsync_lib::get_lib().setup_crypto_ = false; @@ -137,6 +161,51 @@ int main(int argc, char **argv) { vm["mountpoint"].as()); } + if (vm.count("cache-size")) { + uint64_t cache_size_gb = vm["cache-size"].as(); + /* Validate cache size: minimum 1GB, maximum 1TB */ + if (cache_size_gb < 1 || cache_size_gb > 1024) { + std::cerr << "error: cache-size must be between 1 and 1024 GB" << std::endl; + return 1; + } + uint64_t cache_size_bytes = cache_size_gb * 1024ULL * 1024ULL * 1024ULL; + char cache_size_str[32]; + snprintf(cache_size_str, sizeof(cache_size_str), "%llu", + (unsigned long long)cache_size_bytes); + setenv("PCLOUD_CACHE_SIZE", cache_size_str, 1); + } + + if (vm.count("log-path")) { + std::string log_path = vm["log-path"].as(); + /* Validate log path: must not be empty or start with /etc or /sys */ + if (log_path.empty() || log_path.compare(0, 5, "/etc/") == 0 || log_path.compare(0, 5, "/sys/") == 0) { + std::cerr << "error: invalid log-path" << std::endl; + return 1; + } + setenv("PCLOUD_LOG_PATH", log_path.c_str(), 1); + } + + if (vm.count("log-level")) { + setenv("PCLOUD_LOG_LEVEL", vm["log-level"].as().c_str(), 1); + } else { + /* Set default log level to INFO */ + setenv("PCLOUD_LOG_LEVEL", "INFO", 1); + } + + if (vm.count("fs-event-log")) { + std::string fs_event_log = vm["fs-event-log"].as(); + /* Validate fs-event-log path: must not be empty or start with /etc or /sys */ + if (fs_event_log.empty() || fs_event_log.compare(0, 5, "/etc/") == 0 || fs_event_log.compare(0, 5, "/sys/") == 0) { + std::cerr << "error: invalid fs-event-log" << std::endl; + return 1; + } + setenv("PCLOUD_FS_EVENT_LOG", fs_event_log.c_str(), 1); + } + + if (vm.count("fuse-opts")) { + setenv("PCLOUD_FUSE_OPTS", vm["fuse-opts"].as().c_str(), 1); + } + cc::clibrary::pclsync_lib::get_lib().newuser_ = newuser; cc::clibrary::pclsync_lib::get_lib().set_savepass(save_pass); cc::clibrary::pclsync_lib::get_lib().set_daemon(daemon); diff --git a/pcloudcc.logrotate b/pcloudcc.logrotate new file mode 100644 index 0000000..2332ca6 --- /dev/null +++ b/pcloudcc.logrotate @@ -0,0 +1,47 @@ +# Logrotate configuration for pcloudcc +# Install to: /etc/logrotate.d/pcloudcc +# +# For system-wide logs (when using --log-path /var/log/pcloudcc.log) +/var/log/pcloudcc.log /var/log/pcloudcc-fs-events.log { + daily + rotate 7 + compress + delaycompress + missingok + notifempty + create 0644 root root + dateext + dateformat -%Y%m%d + maxsize 100M + + postrotate + # Send USR2 signal to pcloudcc if it's running + # This causes the daemon to reopen both log files + # SIGUSR2 is used because SIGHUP exits the daemon and SIGUSR1 is for debug dumps + pkill -USR2 pcloudcc 2>/dev/null || true + endscript +} + +# For user-specific logs in home directories +# Note: These need to be configured per-user since logrotate typically runs as root +# Users can add their own logrotate configs or use this template +# +# Example for user-specific log rotation (requires user cron job): +# Create ~/.logrotate.conf with: +# +# /home/username/.pcloud/debug.log /home/username/.pcloud/fs-events.log { +# weekly +# rotate 4 +# compress +# missingok +# notifempty +# create 0600 username username +# maxsize 50M +# +# postrotate +# pkill -USR2 -u username pcloudcc 2>/dev/null || true +# endscript +# } +# +# Then add to user's crontab: +# 0 2 * * * /usr/sbin/logrotate -s ~/.logrotate.state ~/.logrotate.conf diff --git a/pclsync/pdbg.c b/pclsync/pdbg.c index 0620647..b0f7d9d 100644 --- a/pclsync/pdbg.c +++ b/pclsync/pdbg.c @@ -1,17 +1,77 @@ #include +#include #include #include #include #include +#include #include #include "pdbg.h" #include "ppath.h" #include "putil.h" -extern PSYNC_THREAD const char *psync_thread_name; +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') { + pdbg_runtime_level = D_INFO; /* default to INFO */ + return; + } + + /* Parse log level string (case-insensitive) */ + if (strcasecmp(level_str, "NONE") == 0) { + pdbg_runtime_level = D_NONE; + } else if (strcasecmp(level_str, "BUG") == 0 || strcasecmp(level_str, "DEBUG") == 0) { + pdbg_runtime_level = D_BUG; + } else if (strcasecmp(level_str, "CRITICAL") == 0) { + pdbg_runtime_level = D_CRITICAL; + } else if (strcasecmp(level_str, "ERROR") == 0) { + pdbg_runtime_level = D_ERROR; + } else if (strcasecmp(level_str, "WARNING") == 0 || strcasecmp(level_str, "WARN") == 0) { + pdbg_runtime_level = D_WARNING; + } else if (strcasecmp(level_str, "INFO") == 0) { + pdbg_runtime_level = D_INFO; + } else if (strcasecmp(level_str, "NOTICE") == 0) { + pdbg_runtime_level = D_NOTICE; + } else { + /* Invalid level, default to INFO */ + pdbg_runtime_level = D_INFO; + } +} char *psync_debug_path() { + const char *custom_path = getenv("PCLOUD_LOG_PATH"); + if (custom_path && custom_path[0] != '\0') { + size_t len = strlen(custom_path) + 1; + char *path = (char *)malloc(len); + if (!path) { + return NULL; + } + snprintf(path, len, "%s", custom_path); + return path; + } + char *home = ppath_home(); if (!home) { return NULL; @@ -28,7 +88,24 @@ char *psync_debug_path() { return sockpath; } +char *psync_fs_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 *)malloc(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, ...) { + /* Initialize debug level from environment on first call */ + pdbg_init_level(); + if (!IS_DEBUG) return 1; @@ -36,7 +113,6 @@ int pdbg_printf(const char *file, const char *function, int unsigned line, int u unsigned long level; const char *name; } debug_levels[] = DEBUG_LEVELS; - static FILE *log = NULL; struct timespec ts; char dttime[36], format[512]; va_list ap; @@ -50,13 +126,39 @@ int pdbg_printf(const char *file, const char *function, int unsigned line, int u errname = debug_levels[i].name; break; } - if (unlikely(!log)) { - char *path = psync_debug_path(); - log = fopen(path, "a+"); - free(path); - if (!log) - return 1; + + 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(); + log_file = fopen(path, "a+"); + free(path); + if (!log_file) { + 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 = psync_fs_event_log_path(); + if (path) { + /* Create empty file if it doesn't exist, or open existing */ + FILE *f = fopen(path, "a+"); + if (f) { + fclose(f); + } + free(path); + } + } + clock_gettime(CLOCK_REALTIME, &ts); time_format(ts.tv_sec, ts.tv_nsec, dttime); threadid = pthread_self(); @@ -65,8 +167,99 @@ int pdbg_printf(const char *file, const char *function, int unsigned line, int u dttime, u, psync_thread_name, errname, file, line, function, fmt); format[sizeof(format) - 1] = 0; va_start(ap, fmt); - vfprintf(log, format, ap); + vfprintf(log_file, format, ap); va_end(ap); - fflush(log); + fflush(log_file); + pthread_mutex_unlock(&log_mutex); 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; + + /* 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) { + new_log = fopen(path, "a+"); + free(path); + 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); + } + } + + /* Also reopen fs event log if it was open */ + if (fs_event_log) { + fclose(fs_event_log); + fs_event_log = NULL; + + path = psync_fs_event_log_path(); + if (path) { + new_log = fopen(path, "a+"); + free(path); + if (new_log) { + fs_event_log = new_log; + } + } + } +} + +/* 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 = psync_fs_event_log_path(); + if (!path) { + /* FS event log not configured, skip */ + pthread_mutex_unlock(&log_mutex); + return; + } + fs_event_log = fopen(path, "a+"); + free(path); + if (!fs_event_log) { + pthread_mutex_unlock(&log_mutex); + return; + } + } + + /* Get timestamp */ + clock_gettime(CLOCK_REALTIME, &ts); + 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); } \ No newline at end of file diff --git a/pclsync/pdbg.h b/pclsync/pdbg.h index 76fd56a..fb4efed 100644 --- a/pclsync/pdbg.h +++ b/pclsync/pdbg.h @@ -11,18 +11,23 @@ extern "C" { #define D_NONE 0 -#define D_BUG 10 -#define D_CRITICAL 20 -#define D_ERROR 30 -#define D_WARNING 40 +#define D_CRITICAL 10 +#define D_ERROR 20 +#define D_WARNING 30 +#define D_INFO 40 #define D_NOTICE 50 +#define D_BUG 60 -#define DEBUG_LEVELS {{D_BUG, "BUG"}, {D_CRITICAL, "CRITICAL ERROR"}, {D_ERROR, "ERROR"}, {D_WARNING, "WARNING"}, {D_NOTICE, "NOTICE" } } +#define DEBUG_LEVELS {{D_BUG, "DEBUG"}, {D_CRITICAL, "CRITICAL ERROR"}, {D_ERROR, "ERROR"}, {D_WARNING, "WARNING"}, {D_INFO, "INFO"}, {D_NOTICE, "NOTICE" } } +/* Compile-time debug level for conditional compilation */ #ifndef DEBUG_LEVEL #define DEBUG_LEVEL D_NOTICE #endif +/* Runtime debug level - can be set via environment variable */ +extern unsigned int pdbg_runtime_level; + #define IS_DEBUG (DEBUG_LEVEL >= D_WARNING) #if defined(assert) @@ -31,7 +36,7 @@ extern "C" { #define pdbg_logf(level, ...) \ do { \ - if (level <= DEBUG_LEVEL) \ + if (level <= pdbg_runtime_level) \ pdbg_printf(__FILE__, __FUNCTION__, __LINE__, level, __VA_ARGS__); \ } while (0) #define pdbg_assert(cond) \ @@ -92,6 +97,8 @@ extern "C" { #endif int pdbg_printf(const char *file, const char *function, int unsigned line, int unsigned level, const char *fmt, ...) PSYNC_COLD PSYNC_FORMAT(printf, 5, 6) PSYNC_NONNULL(5); +void pdbg_reopen_log(void); +void pdbg_write_fs_event(const char *fmt, ...) PSYNC_FORMAT(printf, 1, 2) PSYNC_NONNULL(1); #ifdef __cplusplus } diff --git a/pclsync/pdiff.c b/pclsync/pdiff.c index 3e46045..f888af4 100644 --- a/pclsync/pdiff.c +++ b/pclsync/pdiff.c @@ -920,6 +920,21 @@ static void process_createfolder(const binresult *entry) { psql_bind_uint(st2, 1, mtime); psql_bind_uint(st2, 2, parentfolderid); psql_run(st2); + + // Log remote folder creation (both synced and FUSE-only) + { + char *folderpath = psync_fs_get_path_by_folderid(folderid); + if (likely(folderpath)) { + pdbg_write_fs_event("folder created %s", folderpath); + free(folderpath); + } + folderpath = pfolder_path(folderid, NULL); + if (likely(folderpath)) { + pdbg_logf(D_NOTICE, "remote folder created %s", folderpath); + free(folderpath); + } + } + if (psyncer_dl_has_folder(parentfolderid) && !psync_is_name_to_ignore(name->str)) { psyncer_dl_queue_add(folderid); @@ -1075,6 +1090,10 @@ static void process_modifyfolder(const binresult *entry) { psync_delete_backup_device(folderid); } + // Get old folder path before database update (for move detection) + char *oldfolderpath = psync_fs_get_path_by_folderid(folderid); + char *oldfolderpath_rel = pfolder_path(folderid, NULL); + mtime = papi_find_result2(meta, "modified", PARAM_NUM)->num; psql_bind_uint(st, 1, parentfolderid); psql_bind_uint(st, 2, userid); @@ -1087,6 +1106,27 @@ static void process_modifyfolder(const binresult *entry) { psql_bind_uint(st, 8, folderid); psql_run(st); + // Log folder move/rename if location or name changed + { + int is_move = (oldparentfolderid != parentfolderid || + strcmp(name->str, oldname) != 0); + if (is_move) { + char *newfolderpath = psync_fs_get_path_by_folderid(folderid); + if (likely(oldfolderpath && newfolderpath)) { + pdbg_write_fs_event("folder moved %s -> %s", oldfolderpath, newfolderpath); + } + free(newfolderpath); + + char *newfolderpath_rel = pfolder_path(folderid, NULL); + if (likely(oldfolderpath_rel && newfolderpath_rel)) { + pdbg_logf(D_NOTICE, "remote folder moved %s -> %s", oldfolderpath_rel, newfolderpath_rel); + } + free(newfolderpath_rel); + } + free(oldfolderpath); + free(oldfolderpath_rel); + } + if (oldparentfolderid != parentfolderid) { res = psql_prepare( "UPDATE folder SET subdircnt=subdircnt-1, mtime=? WHERE id=?"); @@ -1235,6 +1275,11 @@ static void process_deletefolder(const binresult *entry) { meta = papi_find_result2(entry, "metadata", PARAM_HASH); folderid = papi_find_result2(meta, "folderid", PARAM_NUM)->num; ppathstatus_fldr_deleted(folderid); + + // Get folder path BEFORE deletion from database + path = pfolder_path(folderid, NULL); + char *fullpath = psync_fs_get_path_by_folderid(folderid); + if (psyncer_dl_has_folder(folderid)) { psyncer_dl_queue_del(folderid); res = psql_query( @@ -1247,9 +1292,9 @@ static void process_deletefolder(const binresult *entry) { psql_bind_uint(stmt, 2, folderid); psql_run_free(stmt); if (psql_affected() == 1) { - path = pfolder_path(folderid, NULL); - ptask_ldir_rm(row[0], folderid, row[1], path); - free(path); + char *syncpath = pfolder_path(folderid, NULL); + ptask_ldir_rm(row[0], folderid, row[1], syncpath); + free(syncpath); needdownload = 1; } } @@ -1258,6 +1303,13 @@ static void process_deletefolder(const binresult *entry) { psql_bind_uint(st, 1, folderid); psql_run(st); if (psql_affected()) { + // Log remote folder deletion (both synced and FUSE-only) + if (likely(fullpath)) { + pdbg_write_fs_event("folder deleted %s", fullpath); + } + if (likely(path)) { + pdbg_logf(D_NOTICE, "remote folder deleted %s", path); + } psql_bind_uint(st2, 1, papi_find_result2(meta, "modified", PARAM_NUM)->num); psql_bind_uint( @@ -1265,6 +1317,10 @@ static void process_deletefolder(const binresult *entry) { psql_run(st2); psync_fs_folder_deleted(folderid); } + if (path) + free(path); + if (fullpath) + free(fullpath); } static void check_for_deletedfileid(const binresult *meta) { @@ -1392,6 +1448,21 @@ static void process_createfile(const binresult *entry) { } insert_revision(fileid, hash, papi_find_result2(meta, "modified", PARAM_NUM)->num, size); + + // Log all remote file creations (both synced and FUSE-only) + { + char *filepath = psync_fs_get_path_by_fileid(fileid); + if (likely(filepath)) { + pdbg_write_fs_event("file created %s", filepath); + free(filepath); + } + filepath = pfolder_file_path(fileid, NULL); + if (likely(filepath)) { + pdbg_logf(D_NOTICE, "remote file created %s", filepath); + free(filepath); + } + } + if (psyncer_dl_has_folder(parentfolderid) && !psync_is_name_to_ignore(name->str)) { res = psql_query("SELECT syncid, localfolderid FROM syncedfolder " @@ -1467,6 +1538,13 @@ static void process_modifyfile(const binresult *entry) { oldsize = psync_get_number(row[2]); if (psync_get_number(row[1]) == psync_my_userid) used_quota -= oldsize; + + // Get old path before database update (for move detection) + oldparentfolderid = psync_get_number(row[0]); + oldname = psync_get_lstring(row[4], &oldnamelen); + char *oldfilepath = psync_fs_get_path_by_fileid(fileid); + char *oldfilepath_rel = pfolder_file_path(fileid, NULL); + if (!st) st = psql_prepare( "UPDATE file SET id=?, parentfolderid=?, userid=?, size=?, hash=?, " @@ -1502,7 +1580,41 @@ static void process_modifyfile(const binresult *entry) { psql_run(st); insert_revision(fileid, hash, papi_find_result2(meta, "modified", PARAM_NUM)->num, size); - oldparentfolderid = psync_get_number(row[0]); + + // Log file move/rename or modification (both synced and FUSE-only) + { + int is_move = (oldparentfolderid != parentfolderid || + name->length != oldnamelen || + memcmp(name->str, oldname, oldnamelen) != 0); + int is_content_change = (hash != psync_get_number(row[3]) || size != oldsize); + + if (is_move) { + char *newfilepath = psync_fs_get_path_by_fileid(fileid); + if (likely(oldfilepath && newfilepath)) { + pdbg_write_fs_event("file moved %s -> %s", oldfilepath, newfilepath); + } + free(newfilepath); + + char *newfilepath_rel = pfolder_file_path(fileid, NULL); + if (likely(oldfilepath_rel && newfilepath_rel)) { + pdbg_logf(D_NOTICE, "remote file moved %s -> %s", oldfilepath_rel, newfilepath_rel); + } + free(newfilepath_rel); + } else if (is_content_change) { + char *filepath = psync_fs_get_path_by_fileid(fileid); + if (likely(filepath)) { + pdbg_write_fs_event("file modified %s", filepath); + free(filepath); + } + filepath = pfolder_file_path(fileid, NULL); + if (likely(filepath)) { + pdbg_logf(D_NOTICE, "remote file modified %s", filepath); + free(filepath); + } + } + free(oldfilepath); + free(oldfilepath_rel); + } oldsync = psyncer_dl_has_folder(oldparentfolderid); if (oldparentfolderid == parentfolderid) newsync = oldsync; @@ -1517,11 +1629,12 @@ static void process_modifyfile(const binresult *entry) { path = pfolder_file_path(fileid, NULL); ptask_lfile_rm(fileid, path); free(path); + free(oldfilepath); + free(oldfilepath_rel); needdownload = 1; return; } lneeddownload = hash != psync_get_number(row[3]) || size != oldsize; - oldname = psync_get_lstring(row[4], &oldnamelen); if (lneeddownload) pdownload_tasks_delete(fileid, 0, 0); needrename = oldparentfolderid != parentfolderid || @@ -1622,13 +1735,28 @@ static void process_deletefile(const binresult *entry) { needdownload = 1; } } + // Get path BEFORE deleting from database + path = pfolder_file_path(fileid, NULL); + char *fullpath = psync_fs_get_path_by_fileid(fileid); + psql_bind_uint(st, 1, fileid); psql_run(st); if (psql_affected()) { + // Log all remote file deletions (both synced and FUSE-only) + if (likely(fullpath)) { + pdbg_write_fs_event("file deleted %s", fullpath); + } + if (likely(path)) { + pdbg_logf(D_NOTICE, "remote file deleted %s", path); + } if (papi_find_result2(meta, "ismine", PARAM_BOOL)->num) used_quota -= papi_find_result2(meta, "size", PARAM_NUM)->num; psync_fs_file_deleted(fileid); } + if (path) + free(path); + if (fullpath) + free(fullpath); } static void start_download() { diff --git a/pclsync/pdownload.c b/pclsync/pdownload.c index 0befcc0..2bd8e18 100644 --- a/pclsync/pdownload.c +++ b/pclsync/pdownload.c @@ -861,6 +861,7 @@ static int task_download_file(download_task_t *dt) { // pqevent_queue_sync_event_id(PEVENT_FILE_DOWNLOAD_FINISHED, syncid, name, // fileid); pdbg_logf(D_NOTICE, "file downloaded %s", dt->localname); + pdbg_write_fs_event("file downloaded %s", dt->localname); psync_list_for_each_element_call(&ranges, psync_range_list_t, list, free); if (tmpold) { pfile_delete(tmpold); diff --git a/pclsync/pfs.c b/pclsync/pfs.c index fd55857..dca1a76 100644 --- a/pclsync/pfs.c +++ b/pclsync/pfs.c @@ -3393,11 +3393,42 @@ void psync_fs_refresh_folder(psync_folderid_t folderid) { static char *psync_fuse_get_mountpoint() { struct stat st; char *mp; + int stat_result; + int stat_errno; + mp = psync_strdup(psync_setting_get_string(_PS(fsroot))); - if (stat(mp, &st) && mkdir(mp, PSYNC_DEFAULT_POSIX_FOLDER_MODE)) { + + stat_result = stat(mp, &st); + stat_errno = errno; + + if (stat_result != 0) { + /* Provide detailed error message based on errno */ + if (stat_errno == ENOTCONN) { + pdbg_logf(D_CRITICAL, + "Mount point %s has a stale FUSE mount (Transport endpoint is not connected). " + "Please unmount it first with: fusermount -u %s", mp, mp); + } else if (stat_errno == ENOENT) { + pdbg_logf(D_CRITICAL, + "Mount point %s does not exist. " + "Please create the directory first with: mkdir -p %s", mp, mp); + } else { + pdbg_logf(D_CRITICAL, + "Cannot access mount point %s (errno=%d: %s). " + "Please verify the path exists and is accessible.", + mp, stat_errno, strerror(stat_errno)); + } free(mp); return NULL; } + + /* Verify it's a directory */ + if (!S_ISDIR(st.st_mode)) { + pdbg_logf(D_CRITICAL, + "Mount point %s exists but is not a directory", mp); + free(mp); + return NULL; + } + return mp; } @@ -3438,6 +3469,27 @@ char *psync_fs_get_path_by_folderid(psync_folderid_t folderid) { return ret; } +char *psync_fs_get_path_by_fileid(psync_fileid_t fileid) { + char *mp, *path, *ret; + pthread_mutex_lock(&start_mutex); + if (started == 1) + mp = psync_strdup(psync_current_mountpoint); + else + mp = NULL; + pthread_mutex_unlock(&start_mutex); + if (!mp) + return NULL; + path = pfolder_file_path(fileid, NULL); + if (path == PSYNC_INVALID_PATH) { + free(mp); + return NULL; + } + ret = psync_strcat(mp, path, NULL); + free(mp); + free(path); + return ret; +} + #if IS_DEBUG static void psync_fs_dump_internals() { @@ -3531,6 +3583,11 @@ static void psync_usr1_handler(int sig) { } #endif +static void psync_usr2_handler(int sig) { + /* Signal handler must be signal-safe - just set flag, don't log here */ + pdbg_reopen_log(); +} + static void psync_set_signal(int sig, void (*handler)(int)) { struct sigaction sa; @@ -3553,6 +3610,7 @@ static void psync_setup_signals() { #if IS_DEBUG psync_set_signal(SIGUSR1, psync_usr1_handler); #endif + psync_set_signal(SIGUSR2, psync_usr2_handler); } static void psync_fs_init_once() { @@ -3632,6 +3690,35 @@ static int psync_fs_do_start() { } fuse_opt_add_arg(&args, "-ohard_remove"); + // Add user-specified FUSE options from environment variable + const char *fuse_opts_env = getenv("PCLOUD_FUSE_OPTS"); + if (fuse_opts_env && fuse_opts_env[0] != '\0') { + char *fuse_opts = strdup(fuse_opts_env); + if (fuse_opts) { + char *saveptr = NULL; + char *token = strtok_r(fuse_opts, ",", &saveptr); + while (token) { + // Trim leading/trailing whitespace + while (*token == ' ' || *token == '\t') token++; + char *end = token + strlen(token) - 1; + while (end > token && (*end == ' ' || *end == '\t')) *end-- = '\0'; + + if (strlen(token) > 0) { + if (strlen(token) > 250) { + pdbg_logf(D_WARNING, "FUSE option too long, skipping: %s", token); + } else { + char opt_arg[256]; + snprintf(opt_arg, sizeof(opt_arg), "-o%s", token); + fuse_opt_add_arg(&args, opt_arg); + pdbg_logf(D_NOTICE, "Adding FUSE option: %s", token); + } + } + token = strtok_r(NULL, ",", &saveptr); + } + free(fuse_opts); + } + } + memset(&psync_oper, 0, sizeof(psync_oper)); psync_oper.init = psync_fs_init; @@ -3677,9 +3764,20 @@ static int psync_fs_do_start() { goto err00; mp = psync_fuse_get_mountpoint(); + if (!mp) { + pdbg_logf(D_CRITICAL, + "CRITICAL ERROR: Cannot initialize FUSE filesystem. " + "Mount point is unavailable. See error messages above for details."); + goto err00; + } + psync_fuse_channel = fuse_mount(mp, &args); - if (pdbg_unlikely(!psync_fuse_channel)) + if (pdbg_unlikely(!psync_fuse_channel)) { + pdbg_logf(D_CRITICAL, + "CRITICAL ERROR: fuse_mount() failed for mount point %s. " + "The FUSE filesystem cannot be started.", mp); goto err0; + } psync_fuse = fuse_new(psync_fuse_channel, &args, &psync_oper, sizeof(psync_oper), NULL); if (pdbg_unlikely(!psync_fuse)) diff --git a/pclsync/ppagecache.c b/pclsync/ppagecache.c index 1264c0a..537a075 100644 --- a/pclsync/ppagecache.c +++ b/pclsync/ppagecache.c @@ -272,19 +272,19 @@ psync_pagecache_get_free_page(int runflushcacheinside) { } while (flush_page_running && psync_list_isempty(&free_pages)); } if (psync_list_isempty(&free_pages)) { - pdbg_logf(D_NOTICE, "no free pages, flushing cache"); + pdbg_logf(D_BUG, "no free pages, flushing cache"); pthread_mutex_unlock(&cache_mutex); flush_pages(1); pthread_mutex_lock(&cache_mutex); while (unlikely(psync_list_isempty(&free_pages))) { pthread_mutex_unlock(&cache_mutex); - pdbg_logf(D_NOTICE, "no free pages after flush, sleeping"); + pdbg_logf(D_BUG, "no free pages after flush, sleeping"); psys_sleep_milliseconds(200); flush_pages(1); pthread_mutex_lock(&cache_mutex); } } else - pdbg_logf(D_NOTICE, "waited for a free page"); + pdbg_logf(D_BUG, "waited for a free page"); page = psync_list_remove_head_element(&free_pages, psync_cache_page_t, list); } @@ -458,7 +458,7 @@ static int pass_shared_api(psock_t *api) { shared_api_waiter_t, list); waiter->api = api; pthread_cond_signal(&waiter->cond); - pdbg_logf(D_NOTICE, "passing shared api connection"); + pdbg_logf(D_BUG, "passing shared api connection"); } pthread_mutex_unlock(&sharedapi_mutex); return ret; @@ -490,7 +490,7 @@ static int wait_shared_api() { pthread_cond_init(&waiter->cond, NULL); waiter->api = NULL; psync_list_add_tail(&sharedapiwaiters, &waiter->list); - pdbg_logf(D_NOTICE, "waiting for shared API connection"); + pdbg_logf(D_BUG, "waiting for shared API connection"); do { pthread_cond_wait(&waiter->cond, &sharedapi_mutex); } while (!waiter->api); @@ -498,7 +498,7 @@ static int wait_shared_api() { pdbg_assertw(waiter->api == (psock_t *)-1); ret = -1; } else { - pdbg_logf(D_NOTICE, "waited for shared API connection"); + pdbg_logf(D_BUG, "waited for shared API connection"); ret = 0; } pthread_mutex_unlock(&sharedapi_mutex); @@ -571,7 +571,7 @@ static int get_urls(psync_request_t *request, psync_urls_t *urls) { totalreqlen = 0; psync_list_for_each_element(range, &request->ranges, psync_request_range_t, list) { - pdbg_logf(D_NOTICE, "sending request for offset %lu, size %lu to API", + pdbg_logf(D_BUG, "sending request for offset %lu, size %lu to API", (unsigned long)range->offset, (unsigned long)range->length); if (unlikely(psync_api_send_read_request(api, request->fileid, request->hash, range->offset, @@ -595,7 +595,7 @@ static int get_urls(psync_request_t *request, psync_urls_t *urls) { break; } hosts = papi_find_result2(ret, "hosts", PARAM_ARRAY); - pdbg_logf(D_NOTICE, "got file URLs of fileid %lu, hash %lu", + pdbg_logf(D_BUG, "got file URLs of fileid %lu, hash %lu", (unsigned long)request->fileid, (unsigned long)request->hash); if (pdbg_likely(hosts->length && hosts->array[0]->type == PARAM_STR) && request->of->initialsize > totalreqlen) @@ -616,7 +616,7 @@ static int get_urls(psync_request_t *request, psync_urls_t *urls) { ret); if (pdbg_unlikely(psync_crypto_is_error(enc))) goto err4; - pdbg_logf(D_NOTICE, "got key for fileid %lu", (unsigned long)request->fileid); + pdbg_logf(D_BUG, "got key for fileid %lu", (unsigned long)request->fileid); free(ret); psync_fs_lock_file(request->of); if (pdbg_likely(request->of->encoder == @@ -634,7 +634,7 @@ static int get_urls(psync_request_t *request, psync_urls_t *urls) { if (psync_pagecache_read_range_from_api(request, range, api)) goto err2; psync_list_del(l1); - pdbg_logf(D_NOTICE, "request for offset %lu, size %lu read from API", + pdbg_logf(D_BUG, "request for offset %lu, size %lu read from API", (unsigned long)range->offset, (unsigned long)range->length); free(range); } @@ -2452,7 +2452,7 @@ retry: psock_set_write_buffered(api); psync_list_for_each_element(range, &request->ranges, psync_request_range_t, list) { - pdbg_logf(D_NOTICE, "sending request for offset %lu, size %lu to API", + pdbg_logf(D_BUG, "sending request for offset %lu, size %lu to API", (unsigned long)range->offset, (unsigned long)range->length); if (psync_api_send_read_request(api, request->fileid, request->hash, range->offset, range->length)) @@ -2856,10 +2856,10 @@ static void wait_waiter(psync_page_waiter_t *pwt, uint64_t hash, const char *pt) { lock_wait(hash); while (!pwt->ready) { - pdbg_logf(D_NOTICE, "waiting for %s page #%lu to be read", pt, + pdbg_logf(D_BUG, "waiting for %s page #%lu to be read", pt, (unsigned long)pwt->waiting_for->pageid); pthread_cond_wait(&pwt->cond, &wait_page_mutex); - pdbg_logf(D_NOTICE, "waited for %s page", + pdbg_logf(D_BUG, "waited for %s page", pt); // not safe to use pwt->waiting_for here } unlock_wait(hash); diff --git a/pclsync/psql.c b/pclsync/psql.c index c0bdce4..fc1e02b 100644 --- a/pclsync/psql.c +++ b/pclsync/psql.c @@ -242,8 +242,12 @@ void psql_do_lock(const char *file, unsigned line) { clock_gettime(CLOCK_REALTIME, &end); msec = (end.tv_sec - start.tv_sec) * 1000 + end.tv_nsec / 1000000 - start.tv_nsec / 1000000; - if (msec >= 5) + if (msec >= 1000) + pdbg_logf(D_ERROR, "waited %lu milliseconds for database write lock", msec); + else if (msec >= 250) pdbg_logf(D_WARNING, "waited %lu milliseconds for database write lock", msec); + else if (msec >= 5) + pdbg_logf(D_BUG, "waited %lu milliseconds for database write lock", msec); pdbg_assert(lockctr == 0); lockctr++; memcpy(&lockstart, &end, sizeof(struct timespec)); @@ -270,8 +274,12 @@ void psql_do_rdlock(const char *file, unsigned line) { clock_gettime(CLOCK_REALTIME, &end); msec = (end.tv_sec - start.tv_sec) * 1000 + end.tv_nsec / 1000000 - start.tv_nsec / 1000000; - if (msec >= 5) + if (msec >= 1000) + pdbg_logf(D_ERROR, "waited %lu milliseconds for database read lock", msec); + else if (msec >= 250) pdbg_logf(D_WARNING, "waited %lu milliseconds for database read lock", msec); + else if (msec >= 5) + pdbg_logf(D_BUG, "waited %lu milliseconds for database read lock", msec); rdlockctr++; memcpy(&rdlockstart, &end, sizeof(struct timespec)); record_rdlock(file, line, &rdlockstart); @@ -430,10 +438,18 @@ void psql_unlock() { clock_gettime(CLOCK_REALTIME, &end); msec = (end.tv_sec - lockstart.tv_sec) * 1000 + end.tv_nsec / 1000000 - lockstart.tv_nsec / 1000000; - if (msec >= 10) + if (msec >= 2000) + pdbg_logf(D_ERROR, + "held database write lock for %lu milliseconds taken from %s:%u", + msec, wrlockfile, wrlockline); + else if (msec >= 500) pdbg_logf(D_WARNING, "held database write lock for %lu milliseconds taken from %s:%u", msec, wrlockfile, wrlockline); + else if (msec >= 10) + pdbg_logf(D_BUG, + "held database write lock for %lu milliseconds taken from %s:%u", + msec, wrlockfile, wrlockline); record_wrunlock(); plocks_unlock(&dblock); } else @@ -487,10 +503,18 @@ void psql_rdunlock() { lock = record_rdunlock(); msec = (end.tv_sec - rdlockstart.tv_sec) * 1000 + end.tv_nsec / 1000000 - rdlockstart.tv_nsec / 1000000; - if (msec >= 20) + if (msec >= 2000) + pdbg_logf(D_ERROR, + "held database read lock for %lu milliseconds taken at %s:%u", msec, + lock->file, lock->line); + else if (msec >= 500) pdbg_logf(D_WARNING, "held database read lock for %lu milliseconds taken at %s:%u", msec, lock->file, lock->line); + else if (msec >= 20) + pdbg_logf(D_BUG, + "held database read lock for %lu milliseconds taken at %s:%u", msec, + lock->file, lock->line); free(lock); } else plocks_unlock(&dblock); diff --git a/pclsync/pssl.c b/pclsync/pssl.c index cc3049b..1398724 100644 --- a/pclsync/pssl.c +++ b/pclsync/pssl.c @@ -343,8 +343,8 @@ int pssl_connect(int sock, void **sslconn, goto err0; } - mbedtls_ssl_conf_max_tls_version(&conn->cfg, MBEDTLS_SSL_VERSION_TLS1_2); - mbedtls_ssl_conf_min_tls_version(&conn->cfg, MBEDTLS_SSL_VERSION_TLS1_2); + mbedtls_ssl_conf_max_version(&conn->cfg, MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_3); + mbedtls_ssl_conf_min_version(&conn->cfg, MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_3); mbedtls_ssl_conf_endpoint(&conn->cfg, MBEDTLS_SSL_IS_CLIENT); mbedtls_ssl_conf_dbg(&conn->cfg, debug_cb, debug_ctx); @@ -673,13 +673,12 @@ psymkey_generate(const char *password, size_t keylen, keylen + offsetof(psync_symmetric_key_struct_t, key)); mbedtls_md_context_t ctx; mbedtls_md_init(&ctx); - mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(MBEDTLS_MD_SHA512), 1); + const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA512); + mbedtls_md_setup(&ctx, md_info, 1); key->keylen = keylen; - const mbedtls_md_info_t *md_info = mbedtls_md_info_from_ctx(&ctx); - mbedtls_md_type_t md_type = mbedtls_md_get_type(md_info); - mbedtls_pkcs5_pbkdf2_hmac_ext(md_type, (const unsigned char *)password, - strlen(password), salt, saltlen, iterations, keylen, - key->key); + mbedtls_pkcs5_pbkdf2_hmac(&ctx, (const unsigned char *)password, + strlen(password), salt, saltlen, iterations, + keylen, key->key); mbedtls_md_free(&ctx); return key; } @@ -700,10 +699,9 @@ char *psymkey_derive(const char *username, psync_sha512(usercopy, userlen, usersha512); free(usercopy); mbedtls_md_init(&ctx); - mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(MBEDTLS_MD_SHA512), 1); - const mbedtls_md_info_t *md_info = mbedtls_md_info_from_ctx(&ctx); - mbedtls_md_type_t md_type = mbedtls_md_get_type(md_info); - mbedtls_pkcs5_pbkdf2_hmac_ext(md_type, (const unsigned char *)passphrase, strlen(passphrase), usersha512, + const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA512); + mbedtls_md_setup(&ctx, md_info, 1); + mbedtls_pkcs5_pbkdf2_hmac(&ctx, (const unsigned char *)passphrase, strlen(passphrase), usersha512, PSYNC_SHA512_DIGEST_LEN, 5000, sizeof(passwordbin), passwordbin); mbedtls_md_free(&ctx); usercopy = psync_base64_encode(passwordbin, sizeof(passwordbin), &userlen); @@ -741,8 +739,8 @@ psync_symmetric_key_t prsa_decrypt_data(psync_rsa_privatekey_t rsa, psync_symmetric_key_t ret; size_t len; if (mbedtls_rsa_rsaes_oaep_decrypt(rsa, rng_get, - &rng, NULL, - 0, &len, data, buff, sizeof(buff))) + &rng, + NULL, 0, &len, data, buff, sizeof(buff))) return PSYNC_INVALID_SYM_KEY; ret = (psync_symmetric_key_t)malloc( offsetof(psync_symmetric_key_struct_t, key) + len); @@ -803,6 +801,7 @@ prsa_sign_sha256_hash(psync_rsa_privatekey_t rsa, if (!ret) return (psync_rsa_signature_t)(void *)PERROR_NO_MEMORY; ret->datalen = rsalen; + /* Save current padding settings using getter functions */ padding = mbedtls_rsa_get_padding_mode(rsa); hash_id = mbedtls_rsa_get_md_alg(rsa); mbedtls_rsa_set_padding(rsa, MBEDTLS_RSA_PKCS_V21, MBEDTLS_MD_SHA256); diff --git a/pclsync/psynclib.h b/pclsync/psynclib.h index 708013c..e8700d1 100644 --- a/pclsync/psynclib.h +++ b/pclsync/psynclib.h @@ -1158,6 +1158,7 @@ void psync_fs_stop(); char *psync_fs_getmountpoint(); void psync_fs_register_start_callback(psync_generic_callback_t callback); char *psync_fs_get_path_by_folderid(psync_folderid_t folderid); +char *psync_fs_get_path_by_fileid(psync_fileid_t fileid); void psync_fs_clean_read_cache(); int psync_fs_move_cache(const char *path); diff --git a/pclsync/putil.c b/pclsync/putil.c index 9f41445..58dad8d 100644 --- a/pclsync/putil.c +++ b/pclsync/putil.c @@ -275,8 +275,12 @@ unsigned char *psync_base64_encode(const unsigned char *str, size_t length, if (length > 1) { *p++ = base64_table[((current[0] & 0x03) << 4) + (current[1] >> 4)]; *p++ = base64_table[(current[1] & 0x0f) << 2]; - } else + *p++ = '='; /* Add padding */ + } else { *p++ = base64_table[(current[0] & 0x03) << 4]; + *p++ = '='; /* Add padding */ + *p++ = '='; /* Add padding */ + } } *ret_length = p - result; diff --git a/pclsync_lib.cpp b/pclsync_lib.cpp index f4b6088..fb5f0b1 100644 --- a/pclsync_lib.cpp +++ b/pclsync_lib.cpp @@ -46,6 +46,9 @@ #include "pclsync/pdevice.h" #include "pclsync/pcommands.h" #include "pclsync/putil.h" +#include "pclsync/pdbg.h" +#include "pclsync/psock.h" +#include "pclsync/plibs.h" #include "pclsync_lib.h" @@ -56,6 +59,47 @@ static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER; static const std::string client_name = "pCloud CC v3.0.0"; +static const char *psync_error_to_string(uint32_t error_code) { + switch (error_code) { + case PERROR_LOCAL_FOLDER_NOT_FOUND: + return "local folder not found"; + case PERROR_REMOTE_FOLDER_NOT_FOUND: + return "remote folder not found"; + case PERROR_DATABASE_OPEN: + return "failed to open database"; + case PERROR_NO_HOMEDIR: + return "cannot determine home directory or create .pcloud directory"; + case PERROR_SSL_INIT_FAILED: + return "SSL initialization failed"; + case PERROR_DATABASE_ERROR: + return "database error"; + case PERROR_LOCAL_FOLDER_ACC_DENIED: + return "local folder access denied"; + case PERROR_REMOTE_FOLDER_ACC_DENIED: + return "remote folder access denied"; + case PERROR_FOLDER_ALREADY_SYNCING: + return "folder already syncing"; + case PERROR_INVALID_SYNCTYPE: + return "invalid sync type"; + case PERROR_OFFLINE: + return "offline"; + case PERROR_INVALID_SYNCID: + return "invalid sync ID"; + case PERROR_PARENT_OR_SUBFOLDER_ALREADY_SYNCING: + return "parent or subfolder already syncing"; + case PERROR_LOCAL_IS_ON_PDRIVE: + return "local path is on pCloud Drive"; + case PERROR_NO_MEMORY: + return "out of memory"; + case PERROR_NET_ERROR: + return "network error"; + case PERROR_PARENT_IS_IGNORED: + return "parent folder is ignored"; + default: + return "unknown error"; + } +} + clib::pclsync_lib::pclsync_lib() : setup_crypto_(false), status_(new pstatus_struct_()), was_init_(false) {} @@ -96,6 +140,9 @@ void clib::pclsync_lib::set_tfa_code(const std::string &arg) { void clib::pclsync_lib::set_username(const std::string &arg) { username_ = arg; } +void clib::pclsync_lib::set_password(const std::string &arg) { + password_ = arg; +} void clib::pclsync_lib::set_crypto_pass(const std::string &arg) { crypto_pass_ = arg; }; @@ -499,7 +546,8 @@ int clib::pclsync_lib::init() { } if (psync_init()) { - std::cout << "init failed\n"; + std::cerr << "Initialization failed: " << psync_error_to_string(psync_error) + << " (error code: " << psync_error << ")" << std::endl; return 1; } @@ -509,6 +557,16 @@ int clib::pclsync_lib::init() { psync_set_string_setting("fsroot", get_mount().c_str()); } + const char *cache_size_env = getenv("PCLOUD_CACHE_SIZE"); + if (cache_size_env && cache_size_env[0] != '\0') { + try { + uint64_t cache_size = std::stoull(cache_size_env); + psync_set_uint_setting("fscachesize", cache_size); + } catch (const std::exception &e) { + pdbg_logf(D_ERROR, "Invalid PCLOUD_CACHE_SIZE: %s", cache_size_env); + } + } + psync_start_sync(status_change, event_handler); username_old = psync_get_username(); diff --git a/pclsync_lib.h b/pclsync_lib.h index 039308f..4458290 100644 --- a/pclsync_lib.h +++ b/pclsync_lib.h @@ -68,6 +68,7 @@ public: void set_trusted_device(bool arg); void set_tfa_code(const std::string& arg); void set_username(const std::string &arg); + void set_password(const std::string &arg); void set_crypto_pass(const std::string &arg); void set_mount(const std::string &arg); void set_savepass(bool s);