Compare commits
6 Commits
20260311T2
...
main
| Author | SHA1 | Date |
|---|---|---|
|
|
93a99cd6ad | |
|
|
4f219806be | |
|
|
d0d34f4345 | |
|
|
89cd7b1c1f | |
|
|
7595c485bf | |
|
|
bf11ae3490 |
16
Makefile
16
Makefile
|
|
@ -31,13 +31,13 @@ else
|
|||
endif
|
||||
|
||||
COMMONFLAGS = -fsanitize=address
|
||||
CFLAGS = -fPIC $(COMMONFLAGS) -I./pclsync -I/usr/include $(FUSE_CFLAGS) $(shell pkg-config --cflags $$(pkg-config --list-all | grep -o 'mbedtls[0-9.]*\s' | head -1) 2>/dev/null || pkg-config --cflags mbedtls 2>/dev/null || echo "-I/usr/local/include")
|
||||
CFLAGS = -fPIC $(COMMONFLAGS) -I. -I./pclsync -I/usr/include $(FUSE_CFLAGS) $(shell pkg-config --cflags $$(pkg-config --list-all | grep -E '^mbedtls[^[:space:]]*' | awk '{print $$1}' | head -1) 2>/dev/null || pkg-config --cflags mbedtls 2>/dev/null || echo "-I/usr/local/include")
|
||||
ifneq (,$(filter clang%,$(CC)))
|
||||
CFLAGS += -Wthread-safety
|
||||
endif
|
||||
CXXFLAGS = $(CFLAGS)
|
||||
CXXFLAGS = $(CFLAGS) -std=c++11
|
||||
LIBLDFLAGS = $(COMMONFLAGS) -lreadline -lpthread -ludev -lsqlite3 -lz $(shell \
|
||||
MBEDTLS_PKG=$$(pkg-config --list-all 2>/dev/null | grep -o 'mbedtls[0-9.]*\s' | head -1 | tr -d ' '); \
|
||||
MBEDTLS_PKG=$$(pkg-config --list-all 2>/dev/null | grep -E '^mbedtls[^[:space:]]*' | awk '{print $$1}' | head -1); \
|
||||
if [ -n "$$MBEDTLS_PKG" ]; then \
|
||||
MBEDX509=$$(echo $$MBEDTLS_PKG | sed 's/mbedtls/mbedx509/'); \
|
||||
MBEDCRYPTO=$$(echo $$MBEDTLS_PKG | sed 's/mbedtls/mbedcrypto/'); \
|
||||
|
|
@ -45,7 +45,7 @@ LIBLDFLAGS = $(COMMONFLAGS) -lreadline -lpthread -ludev -lsqlite3 -lz $(shell \
|
|||
else \
|
||||
pkg-config --libs mbedtls mbedx509 mbedcrypto 2>/dev/null || echo "-L/usr/local/lib -lmbedtls -lmbedx509 -lmbedcrypto"; \
|
||||
fi)
|
||||
EXECLDFLAGS = $(COMMONFLAGS) -lboost_program_options $(FUSE_LIBS)
|
||||
EXECLDFLAGS = $(COMMONFLAGS) $(FUSE_LIBS)
|
||||
|
||||
SCAN := 0
|
||||
SRCDIR := .
|
||||
|
|
@ -175,7 +175,8 @@ TEST_BINS := \
|
|||
tests/test_ppagecache \
|
||||
tests/test_pfs_helpers \
|
||||
tests/test_pdiff_helpers \
|
||||
tests/test_plocalscan_helpers
|
||||
tests/test_plocalscan_helpers \
|
||||
tests/test_pcl26j_free
|
||||
|
||||
.PHONY: test tests check clean-tests
|
||||
|
||||
|
|
@ -258,6 +259,11 @@ tests/test_plocalscan_helpers: $(UNIT_DIR)/test_plocalscan_helpers.c $(LIBDIR)/p
|
|||
-Wl,--wrap=psync_send_backup_del_event
|
||||
# ^ GNU ld only; wraps filter/side-effect calls in extracted helpers
|
||||
|
||||
tests/test_pcl26j_free: $(UNIT_DIR)/test_pcl26j_free.c $(LIBDIR)/ptree.c $(LIBDIR)/pmem.c $(LIBDIR)/pdbg.c $(LIBDIR)/putil.c $(LIBDIR)/ppath.c tests/stubs/test_stubs.c
|
||||
$(CC) $(TEST_CFLAGS) $(CFLAGS) -o $@ $^ \
|
||||
-Wl,--wrap=malloc \
|
||||
-Wl,--wrap=free
|
||||
|
||||
tests/test_pdiff_helpers: $(UNIT_DIR)/test_pdiff_helpers.c $(LIBDIR)/pdiff_helpers.c $(LIBDIR)/pdbg.c $(LIBDIR)/pmem.c $(LIBDIR)/putil.c $(LIBDIR)/ppath.c tests/stubs/test_stubs.c
|
||||
$(CC) $(TEST_CFLAGS) $(CFLAGS) -o $@ $^ \
|
||||
-Wl,--wrap=papi_find_result \
|
||||
|
|
|
|||
|
|
@ -198,7 +198,9 @@ void setup_app(CLI::App *app) {
|
|||
size_t errm_size = 0;
|
||||
RpcClient *rpc = new RpcClient();
|
||||
int result = rpc->Call(SENDAUTH, auth_pass_input.c_str(), &errm, &errm_size);
|
||||
putil_wipe(auth_pass_input.data(), auth_pass_input.size());
|
||||
if (!auth_pass_input.empty()) {
|
||||
putil_wipe(&auth_pass_input[0], auth_pass_input.size());
|
||||
}
|
||||
auth_pass_input.clear();
|
||||
if (result != 0) {
|
||||
std::cerr << "Failed to send auth: " << (errm ? errm : "no message") << std::endl;
|
||||
|
|
@ -221,7 +223,9 @@ void setup_app(CLI::App *app) {
|
|||
size_t errm_size = 0;
|
||||
RpcClient *rpc = new RpcClient();
|
||||
int result = rpc->Call(SENDAUTHSAVE, authsave_pass_input.c_str(), &errm, &errm_size);
|
||||
putil_wipe(authsave_pass_input.data(), authsave_pass_input.size());
|
||||
if (!authsave_pass_input.empty()) {
|
||||
putil_wipe(&authsave_pass_input[0], authsave_pass_input.size());
|
||||
}
|
||||
authsave_pass_input.clear();
|
||||
if (result != 0) {
|
||||
std::cerr << "Failed to send auth: " << (errm ? errm : "no message") << std::endl;
|
||||
|
|
@ -440,7 +444,7 @@ void setup_app(CLI::App *app) {
|
|||
}
|
||||
|
||||
int process_command(const std::string &command) {
|
||||
CLI::App app = CLI::App{"pcloudcc-lneely"};
|
||||
CLI::App app{"pcloudcc-lneely"};
|
||||
setup_app(&app);
|
||||
try {
|
||||
app.parse(command);
|
||||
|
|
@ -480,9 +484,6 @@ int process_command(const std::string &command) {
|
|||
}
|
||||
|
||||
void process_commands() {
|
||||
CLI::App app = CLI::App{"pcloudcc-lneely"};
|
||||
setup_app(&app);
|
||||
|
||||
using_history();
|
||||
rl_attempted_completion_function = command_completion;
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ pkgs.mkShell {
|
|||
buildInputs = with pkgs; [
|
||||
zlib
|
||||
sqlite
|
||||
boost
|
||||
libudev-zero
|
||||
readline
|
||||
fuse
|
||||
|
|
@ -15,7 +14,6 @@ pkgs.mkShell {
|
|||
CFLAGS = [
|
||||
"-I${pkgs.zlib.dev}/include"
|
||||
"-I${pkgs.sqlite.dev}/include"
|
||||
"-I${pkgs.boost.dev}/include"
|
||||
"-I${pkgs.readline.dev}/include"
|
||||
"-I${pkgs.fuse.dev}/include"
|
||||
"-I${pkgs.mbedtls}/include"
|
||||
|
|
@ -23,7 +21,6 @@ pkgs.mkShell {
|
|||
CXXFLAGS = [
|
||||
"-I${pkgs.zlib.dev}/include"
|
||||
"-I${pkgs.sqlite.dev}/include"
|
||||
"-I${pkgs.boost.dev}/include"
|
||||
"-I${pkgs.readline.dev}/include"
|
||||
"-I${pkgs.fuse.dev}/include"
|
||||
"-I${pkgs.mbedtls}/include"
|
||||
|
|
|
|||
|
|
@ -93,26 +93,26 @@ case $IMAGE in
|
|||
echo "Setting up Debian/Ubuntu-based container..."
|
||||
ctrid=$(buildah from "$FULL_IMAGE")
|
||||
buildah run "$ctrid" -- apt update
|
||||
buildah run "$ctrid" -- apt install -y build-essential git libfuse-dev libudev-dev libsqlite3-dev libmbedtls-dev zlib1g-dev libboost-system-dev libboost-program-options-dev fuse llvm gdb
|
||||
buildah run "$ctrid" -- apt install -y build-essential git libfuse-dev libudev-dev libsqlite3-dev libmbedtls-dev zlib1g-dev fuse llvm gdb
|
||||
;;
|
||||
fedora)
|
||||
echo "Setting up Fedora-based container..."
|
||||
ctrid=$(buildah from "$FULL_IMAGE")
|
||||
buildah run "$ctrid" -- dnf update -y
|
||||
buildah run "$ctrid" -- dnf group install -y "C Development Tools and Libraries"
|
||||
buildah run "$ctrid" -- dnf install -y git fuse-devel systemd-devel sqlite-devel mbedtls-devel zlib-devel boost-devel boost-program-options fuse llvm gdb fuse udev libasan
|
||||
buildah run "$ctrid" -- dnf install -y git fuse-devel systemd-devel sqlite-devel mbedtls-devel zlib-devel fuse llvm gdb fuse udev libasan
|
||||
;;
|
||||
archlinux|arch)
|
||||
echo "Setting up Arch Linux-based container..."
|
||||
ctrid=$(buildah from "$FULL_IMAGE")
|
||||
buildah run "$ctrid" -- pacman -Syu --noconfirm
|
||||
buildah run "$ctrid" -- pacman -S --noconfirm base-devel git fuse2 systemd sqlite mbedtls2 zlib boost boost-libs llvm gdb udev gcc make
|
||||
buildah run "$ctrid" -- pacman -S --noconfirm base-devel git fuse2 systemd sqlite mbedtls2 zlib llvm gdb udev gcc make
|
||||
;;
|
||||
opensuse/tumbleweed|opensuse/leap)
|
||||
echo "Setting up openSUSE-based container..."
|
||||
ctrid=$(buildah from "$FULL_IMAGE")
|
||||
buildah run "$ctrid" -- zypper refresh
|
||||
buildah run "$ctrid" -- zypper install -y gcc gcc-c++ make git fuse-devel systemd-devel sqlite3-devel zlib-devel libboost_system-devel libboost_program_options-devel fuse llvm gdb udev mbedtls-2-devel
|
||||
buildah run "$ctrid" -- zypper install -y gcc gcc-c++ make git fuse-devel systemd-devel sqlite3-devel zlib-devel fuse llvm gdb udev mbedtls-2-devel
|
||||
;;
|
||||
*)
|
||||
echo "Unsupported image: $IMAGE"
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ build_container() {
|
|||
sudo vim nano curl wget git htop tmux man-db \
|
||||
bash-completion ca-certificates openssh \
|
||||
base-devel gcc gcc-libs make fuse2 systemd sqlite3 \
|
||||
mbedtls zlib boost llvm gdb iproute \
|
||||
mbedtls zlib llvm gdb iproute \
|
||||
rsync readline
|
||||
|
||||
# verify fuse
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ build_container() {
|
|||
sudo vim nano curl wget git htop tmux man-db \
|
||||
bash-completion ca-certificates openssh \
|
||||
base-devel gcc gcc-libs make fuse2 systemd sqlite3 \
|
||||
mbedtls zlib boost llvm gdb iproute \
|
||||
mbedtls zlib llvm gdb iproute \
|
||||
rsync readline
|
||||
|
||||
# verify fuse
|
||||
|
|
|
|||
|
|
@ -106,8 +106,7 @@ build_container() {
|
|||
sudo vim nano curl wget git htop tmux man-db locales \
|
||||
bash-completion ca-certificates ssh systemd systemd-sysv \
|
||||
build-essential libfuse-dev libudev-dev libsqlite3-dev \
|
||||
libmbedtls-dev zlib1g-dev libboost-system-dev \
|
||||
libboost-program-options-dev fuse llvm gdb iproute2 \
|
||||
libmbedtls-dev zlib1g-dev fuse llvm gdb iproute2 \
|
||||
openssh-server rsync libreadline-dev
|
||||
|
||||
# verify fuse
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ build_container() {
|
|||
sudo vim nano curl wget git htop tmux man-db \
|
||||
bash-completion ca-certificates openssh-server \
|
||||
gcc gcc-c++ make fuse-devel systemd-devel sqlite-devel \
|
||||
mbedtls-devel zlib-devel boost-devel fuse llvm gdb iproute \
|
||||
mbedtls-devel zlib-devel fuse llvm gdb iproute \
|
||||
rsync readline-devel
|
||||
|
||||
# verify fuse
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Dependencies
|
||||
- zlib (-lz)
|
||||
- boost (-lboost_system, -lboost_program_options)
|
||||
- CLI11 (included as CLI11.hpp)
|
||||
- pthread (lpthread)
|
||||
- udev (-ludev)
|
||||
- fuse (-lfuse)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
## 2026.05.06 Notice
|
||||
|
||||
This guide is probably losing relevance in most cases, as the latest distributions have now migrated to (or at least support) `mbedtls` 3.x. If you run an older distribution, you can still follow the guide below to install `mbedtls` 3.x side-by-side with any existing 2.x installations.
|
||||
|
||||
## mbedtls 3.x Migration Notice
|
||||
|
||||
`pcloudcc` now uses `mbedtls` version 3.x. This may already be included in your distribution, and if it is, you can ignore this section. If you're unlucky enough that your distribution still ships with `mbedtls` 2.x *(looking at you, Debian...)*, then try the following instructions. This has been tested on debian bookworm, **but you may have to adjust for your own distribution -- the command sequence below uses `apt` to install known build dependencies.**
|
||||
|
|
@ -24,9 +28,9 @@ The symbolic link at the end resolves the ambiguity between `/usr/include/mbedtl
|
|||
```
|
||||
# run from the source root directory (e.g., pcloudcc-lneely)
|
||||
|
||||
sed -i 's/-lmbedtls/-l:libmbedtls.a/;s/-lmbedcrypto/-l:libmbedcrypto.a/;s/-lmbedx509/-l:libmbedx509.a/' Makefile
|
||||
sed -i 's/LIBLDFLAGS\t= \$(COMMONFLAGS)/LIBLDFLAGS\t= $(COMMONFLAGS) -L\/usr\/local\/lib\//' Makefile
|
||||
sed -i '5s/$/ -I\/usr\/local\/include/' Makefile
|
||||
sed -i 's|[$](shell pkg-config --cflags.*|-I/usr/local/include|' Makefile
|
||||
sed -i '/^\tMBEDTLS_PKG/,/^\tfi)/d' Makefile
|
||||
sed -i 's|[$](shell \\$|-L/usr/local/lib -l:libmbedtls.a -l:libmbedx509.a -l:libmbedcrypto.a|' Makefile
|
||||
find . -type f -name "*.[ch]" -exec sed -i 's/#include <mbedtls/#include <mbedtls3/' {} +
|
||||
make clean all
|
||||
```
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@
|
|||
clang-tools
|
||||
zlib
|
||||
sqlite
|
||||
boost
|
||||
libudev-zero
|
||||
readline
|
||||
fuse
|
||||
|
|
@ -27,7 +26,6 @@
|
|||
CFLAGS = [
|
||||
"-I${pkgs.zlib.dev}/include"
|
||||
"-I${pkgs.sqlite.dev}/include"
|
||||
"-I${pkgs.boost.dev}/include"
|
||||
"-I${pkgs.readline.dev}/include"
|
||||
"-I${pkgs.fuse.dev}/include"
|
||||
"-I${pkgs.mbedtls}/include"
|
||||
|
|
@ -36,7 +34,6 @@
|
|||
CXXFLAGS = [
|
||||
"-I${pkgs.zlib.dev}/include"
|
||||
"-I${pkgs.sqlite.dev}/include"
|
||||
"-I${pkgs.boost.dev}/include"
|
||||
"-I${pkgs.readline.dev}/include"
|
||||
"-I${pkgs.fuse.dev}/include"
|
||||
"-I${pkgs.mbedtls}/include"
|
||||
|
|
|
|||
128
main.cpp
128
main.cpp
|
|
@ -28,16 +28,19 @@
|
|||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <cstring>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <boost/program_options.hpp>
|
||||
#include "CLI11.hpp"
|
||||
|
||||
#include "control_tools.h"
|
||||
|
||||
#include "pclsync_lib.h"
|
||||
#include "pclsync/psettings.h"
|
||||
#include "pclsync/psignal.h"
|
||||
#include "pclsync/putil.h"
|
||||
|
||||
namespace po = boost::program_options;
|
||||
namespace ct = control_tools;
|
||||
namespace cc = console_client;
|
||||
|
||||
|
|
@ -48,10 +51,9 @@ int main(int argc, char **argv) {
|
|||
psignal_register(SIGSEGV);
|
||||
psignal_register(SIGABRT);
|
||||
psignal_register(SIGBUS);
|
||||
|
||||
|
||||
std::cout << "pCloud console client (" << version << ")" << std::endl;
|
||||
std::string username = "";
|
||||
std::string password = "";
|
||||
std::string tfa_code = "";
|
||||
bool daemon = false;
|
||||
bool commands = false;
|
||||
|
|
@ -60,43 +62,41 @@ int main(int argc, char **argv) {
|
|||
bool passwordsw = false;
|
||||
bool save_pass = false;
|
||||
bool crypto = false;
|
||||
bool passascrypto_sw = false;
|
||||
bool trusted_device = false;
|
||||
po::variables_map vm;
|
||||
std::string mountpoint = "";
|
||||
uint64_t cache_size_gb = 0;
|
||||
std::string log_path = "";
|
||||
std::string log_level = "";
|
||||
std::string fs_event_log = "";
|
||||
std::string fuse_opts = "";
|
||||
|
||||
CLI::App app{"Allowed options"};
|
||||
app.set_help_flag("-h,--help", "Show this help message.");
|
||||
|
||||
app.add_option("-u,--username", username, "pCloud account name.")
|
||||
->envname("PCLOUD_USER");
|
||||
app.add_flag("-p,--password", passwordsw, "Ask for pCloud account password.");
|
||||
app.add_option("-t,--tfa_code", tfa_code, "pCloud tfa code");
|
||||
app.add_flag("-r,--trusted_device", trusted_device, "Trust this device.");
|
||||
app.add_flag("-c,--crypto", crypto, "Ask for crypto password.");
|
||||
app.add_flag("-y,--passascrypto", passascrypto_sw, "User password is the same as crypto password.");
|
||||
app.add_flag("-d,--daemonize", daemon, "Run the process as a background daemon.");
|
||||
app.add_flag("-o,--commands", commands, "Keep parent process alive and process commands.");
|
||||
app.add_option("-m,--mountpoint", mountpoint, "Specify where pCloud filesystem is mounted.");
|
||||
app.add_flag("-k,--commands_only", commands_only, "Open command prompt to interact with running daemon.");
|
||||
app.add_flag("-n,--newuser", newuser, "Register a new pCloud user account.");
|
||||
app.add_flag("-s,--savepassword", save_pass, "Save user password in the database.");
|
||||
app.add_option("--cache-size", cache_size_gb, "Maximum cache size in GB (default: 5GB).");
|
||||
app.add_option("--log-path", log_path, "Custom path for debug.log (default: ~/.pcloud/debug.log).");
|
||||
app.add_option("--log-level", log_level, "Logging level: NONE, ERROR, WARNING, INFO (default), NOTICE, DEBUG.");
|
||||
app.add_option("--fs-event-log", fs_event_log, "Path to filesystem events log (default: disabled).");
|
||||
app.add_option("-O,--fuse-opts", fuse_opts, "FUSE mount options (e.g., 'allow_other,allow_root').");
|
||||
|
||||
app.allow_extras();
|
||||
|
||||
try {
|
||||
po::options_description desc("Allowed options");
|
||||
desc.add_options()
|
||||
("help,h", "Show this help message.")
|
||||
("username,u", po::value<std::string>(&username), "pCloud account name.")
|
||||
("password,p", po::bool_switch(&passwordsw), "Ask for pCloud account password.")
|
||||
("tfa_code,t", po::value<std::string>(&tfa_code), "pCloud tfa code")
|
||||
("trusted_device,r", po::bool_switch(&trusted_device), "Trust this device.")
|
||||
("crypto,c", po::bool_switch(&crypto), "Ask for crypto password.")
|
||||
("passascrypto,y", po::value<std::string>(), "User password is the same as crypto password.")
|
||||
("daemonize,d", po::bool_switch(&daemon), "Run the process as a background daemon.")
|
||||
("commands ,o", po::bool_switch(&commands), "Keep parent process alive and process commands. ")
|
||||
("mountpoint,m", po::value<std::string>(), "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.")
|
||||
("cache-size", po::value<uint64_t>(), "Maximum cache size in GB (default: 5GB).")
|
||||
("log-path", po::value<std::string>(), "Custom path for debug.log (default: ~/.pcloud/debug.log).")
|
||||
("log-level", po::value<std::string>(), "Logging level: NONE, ERROR, WARNING, INFO (default), NOTICE, DEBUG.")
|
||||
("fs-event-log", po::value<std::string>(), "Path to filesystem events log (default: disabled).")
|
||||
("fuse-opts,O", po::value<std::string>(), "FUSE mount options (e.g., 'allow_other,allow_root').");
|
||||
|
||||
po::command_line_parser parser{argc, argv};
|
||||
po::positional_options_description p;
|
||||
parser.options(desc).positional(p).allow_unregistered();
|
||||
po::parsed_options parsed_options = parser.run();
|
||||
po::store(parsed_options, vm);
|
||||
|
||||
po::notify(vm);
|
||||
|
||||
if (vm.count("help")) {
|
||||
std::cout << desc << "\n";
|
||||
return 0;
|
||||
}
|
||||
app.parse(argc, argv);
|
||||
|
||||
if (commands_only) {
|
||||
ct::process_commands();
|
||||
|
|
@ -104,22 +104,14 @@ int main(int argc, char **argv) {
|
|||
}
|
||||
|
||||
bool has_piped_input = !isatty(STDIN_FILENO);
|
||||
if (has_piped_input && !vm.count("help")) {
|
||||
if (has_piped_input && app.count("-h") == 0 && app.count("--help") == 0) {
|
||||
std::string line;
|
||||
if (std::getline(std::cin, line) && !line.empty()) {
|
||||
return ct::process_command(line);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 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()) {
|
||||
if (username.empty()) {
|
||||
std::cout << "Username option is required, specify with "
|
||||
<< "-u or --username, or set PCLOUD_USER." << std::endl;
|
||||
return 1;
|
||||
|
|
@ -129,7 +121,7 @@ int main(int argc, char **argv) {
|
|||
memset(argv[i], 0, strlen(argv[i]));
|
||||
}
|
||||
if (daemon) {
|
||||
strncpy(argv[0], "pCloudDriveDeamon", strlen(argv[0]));
|
||||
strncpy(argv[0], "pCloudDriveDaemon", strlen(argv[0]));
|
||||
} else {
|
||||
strncpy(argv[0], "pCloudDrive", strlen(argv[0]));
|
||||
}
|
||||
|
|
@ -147,8 +139,8 @@ int main(int argc, char **argv) {
|
|||
cc::clibrary::pclsync_lib::get_lib().set_trusted_device(trusted_device);
|
||||
if (crypto) {
|
||||
cc::clibrary::pclsync_lib::get_lib().setup_crypto_ = true;
|
||||
if (vm.count("passascrypto")) {
|
||||
cc::clibrary::pclsync_lib::get_lib().set_crypto_pass(password);
|
||||
if (passascrypto_sw) {
|
||||
cc::clibrary::pclsync_lib::get_lib().set_crypto_pass(cc::clibrary::pclsync_lib::get_lib().get_password());
|
||||
} else {
|
||||
const char *env_crypto = std::getenv("PCLOUD_CRYPTO_PASSWORD");
|
||||
if (env_crypto && env_crypto[0]) {
|
||||
|
|
@ -161,13 +153,11 @@ int main(int argc, char **argv) {
|
|||
} else
|
||||
cc::clibrary::pclsync_lib::get_lib().setup_crypto_ = false;
|
||||
|
||||
if (vm.count("mountpoint")) {
|
||||
cc::clibrary::pclsync_lib::get_lib().set_mount(
|
||||
vm["mountpoint"].as<std::string>());
|
||||
if (app.count("--mountpoint") > 0 || app.count("-m") > 0) {
|
||||
cc::clibrary::pclsync_lib::get_lib().set_mount(mountpoint);
|
||||
}
|
||||
|
||||
if (vm.count("cache-size")) {
|
||||
uint64_t cache_size_gb = vm["cache-size"].as<uint64_t>();
|
||||
if (app.count("--cache-size") > 0) {
|
||||
/* 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;
|
||||
|
|
@ -180,8 +170,7 @@ int main(int argc, char **argv) {
|
|||
setenv("PCLOUD_CACHE_SIZE", cache_size_str, 1);
|
||||
}
|
||||
|
||||
if (vm.count("log-path")) {
|
||||
std::string log_path = vm["log-path"].as<std::string>();
|
||||
if (app.count("--log-path") > 0) {
|
||||
/* 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;
|
||||
|
|
@ -190,15 +179,14 @@ int main(int argc, char **argv) {
|
|||
setenv("PCLOUD_LOG_PATH", log_path.c_str(), 1);
|
||||
}
|
||||
|
||||
if (vm.count("log-level")) {
|
||||
setenv("PCLOUD_LOG_LEVEL", vm["log-level"].as<std::string>().c_str(), 1);
|
||||
if (app.count("--log-level") > 0) {
|
||||
setenv("PCLOUD_LOG_LEVEL", log_level.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<std::string>();
|
||||
if (app.count("--fs-event-log") > 0) {
|
||||
/* 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;
|
||||
|
|
@ -207,14 +195,16 @@ int main(int argc, char **argv) {
|
|||
setenv("PCLOUD_FS_EVENT_LOG", fs_event_log.c_str(), 1);
|
||||
}
|
||||
|
||||
if (vm.count("fuse-opts")) {
|
||||
setenv("PCLOUD_FUSE_OPTS", vm["fuse-opts"].as<std::string>().c_str(), 1);
|
||||
if (app.count("--fuse-opts") > 0 || app.count("-O") > 0) {
|
||||
setenv("PCLOUD_FUSE_OPTS", fuse_opts.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);
|
||||
} catch (std::exception &e) {
|
||||
} catch (const CLI::ParseError &e) {
|
||||
return app.exit(e);
|
||||
} catch (const std::exception &e) {
|
||||
std::cerr << "error: " << e.what() << std::endl;
|
||||
return 1;
|
||||
} catch (...) {
|
||||
|
|
@ -233,5 +223,13 @@ int main(int argc, char **argv) {
|
|||
}
|
||||
}
|
||||
|
||||
if (!tfa_code.empty()) {
|
||||
putil_wipe(&tfa_code[0], tfa_code.size());
|
||||
}
|
||||
|
||||
cc::clibrary::pclsync_lib::get_lib().wipe_password();
|
||||
cc::clibrary::pclsync_lib::get_lib().wipe_crypto_pass();
|
||||
cc::clibrary::pclsync_lib::get_lib().wipe_tfa_code();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1745,6 +1745,12 @@ static void close_if_valid(int fd) {
|
|||
pfile_close(fd);
|
||||
}
|
||||
|
||||
/* Free a psync_sector_inlog_t node allocated via pmem_malloc. Used as the
|
||||
* callback for ptree_for_each_element_call_safe when bulk-freeing the tree. */
|
||||
static void free_sector_inlog_node(psync_sector_inlog_t *e) {
|
||||
pmem_free(PMEM_SUBSYS_OTHER, e);
|
||||
}
|
||||
|
||||
static void pfs_free_openfile(psync_openfile_t *of) {
|
||||
pdbg_logf(D_NOTICE, "releasing file %s", of->currentname);
|
||||
if (unlikely(of->writetimer != PSYNC_INVALID_TIMER))
|
||||
|
|
@ -1770,7 +1776,7 @@ static void pfs_free_openfile(psync_openfile_t *of) {
|
|||
}
|
||||
close_if_valid(of->logfile);
|
||||
ptree_for_each_element_call_safe(
|
||||
of->sectorsinlog, psync_sector_inlog_t, tree, free);
|
||||
of->sectorsinlog, psync_sector_inlog_t, tree, free_sector_inlog_node);
|
||||
delete_log_files(of);
|
||||
if (of->authenticatedints)
|
||||
psync_interval_tree_free(of->authenticatedints);
|
||||
|
|
|
|||
|
|
@ -2238,9 +2238,15 @@ int ppagecache_read_mod_locked(psync_openfile_t *of, char *buf,
|
|||
return rd;
|
||||
}
|
||||
|
||||
/* Free a psync_request_range_t node allocated via pmem_malloc. Used as the
|
||||
* callback for psync_list_for_each_element_call when bulk-freeing the list. */
|
||||
static void free_request_range(psync_request_range_t *range) {
|
||||
pmem_free(PMEM_SUBSYS_CACHE, range);
|
||||
}
|
||||
|
||||
static void psync_pagecache_free_request(psync_request_t *request) {
|
||||
psync_list_for_each_element_call(&request->ranges, psync_request_range_t,
|
||||
list, free);
|
||||
list, free_request_range);
|
||||
pmem_free(PMEM_SUBSYS_CACHE, request);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -135,6 +135,7 @@ static psync_tree *folder_tasks = PSYNC_TREE_EMPTY;
|
|||
|
||||
static void sync_data_free(sync_data_t *sd);
|
||||
static void load_sync_tasks();
|
||||
static void free_folder_tasks_node(folder_tasks_t *ft);
|
||||
|
||||
static inline int psync_crypto_is_error(const void *ptr) {
|
||||
return (uintptr_t)ptr <= PSYNC_CRYPTO_MAX_ERROR;
|
||||
|
|
@ -162,7 +163,7 @@ void ppathstatus_init() {
|
|||
psync_list_add_tail(&parent_cache_lru, &parent_cache_entries[i].list_lru);
|
||||
psync_list_add_tail(&cache_free, &parent_cache_entries[i].list_hash);
|
||||
}
|
||||
ptree_for_each_element_call_safe(folder_tasks, folder_tasks_t, tree, free);
|
||||
ptree_for_each_element_call_safe(folder_tasks, folder_tasks_t, tree, free_folder_tasks_node);
|
||||
folder_tasks = PSYNC_TREE_EMPTY;
|
||||
ptree_for_each_element_call_safe(sync_data, sync_data_t, tree,
|
||||
sync_data_free);
|
||||
|
|
@ -301,6 +302,12 @@ static void free_folder_tasks(folder_tasks_t *ft) {
|
|||
pmem_free(PMEM_SUBSYS_OTHER, ft);
|
||||
}
|
||||
|
||||
/* Free a folder_tasks_t node allocated via pmem_malloc. Used as the callback
|
||||
* for ptree_for_each_element_call_safe when bulk-freeing a tree. */
|
||||
static void free_folder_tasks_node(folder_tasks_t *ft) {
|
||||
pmem_free(PMEM_SUBSYS_OTHER, ft);
|
||||
}
|
||||
|
||||
static psync_folderid_t get_parent_folder(psync_folderid_t folderid) {
|
||||
psync_sql_res *res;
|
||||
psync_uint_row row;
|
||||
|
|
@ -447,7 +454,7 @@ void ppathstatus_fldr_deleted(psync_folderid_t folderid) {
|
|||
}
|
||||
|
||||
static void sync_data_free(sync_data_t *sd) {
|
||||
ptree_for_each_element_call_safe(sd->folder_tasks, folder_tasks_t, tree, free);
|
||||
ptree_for_each_element_call_safe(sd->folder_tasks, folder_tasks_t, tree, free_folder_tasks_node);
|
||||
pmem_free(PMEM_SUBSYS_OTHER, sd);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -185,9 +185,9 @@ int psql_connect(const char *db) {
|
|||
int initdbneeded = 0;
|
||||
int code;
|
||||
|
||||
pdbg_assert(sqlite3_libversion_number() == SQLITE_VERSION_NUMBER);
|
||||
pdbg_assert(!strcmp(sqlite3_sourceid(), SQLITE_SOURCE_ID));
|
||||
pdbg_assert(!strcmp(sqlite3_libversion(), SQLITE_VERSION));
|
||||
pdbg_assertw(sqlite3_libversion_number() == SQLITE_VERSION_NUMBER);
|
||||
pdbg_assertw(!strcmp(sqlite3_sourceid(), SQLITE_SOURCE_ID));
|
||||
pdbg_assertw(!strcmp(sqlite3_libversion(), SQLITE_VERSION));
|
||||
pdbg_logf(D_NOTICE, "Using sqlite version %s source %s", sqlite3_libversion(),
|
||||
sqlite3_sourceid());
|
||||
if (!sqlite3_threadsafe()) {
|
||||
|
|
|
|||
|
|
@ -67,6 +67,12 @@ static psync_tree *psync_new_sd_folder(psync_folderid_t folderid) {
|
|||
return &f->tree;
|
||||
}
|
||||
|
||||
/* Free a synced_down_folder node allocated via pmem_malloc. Used as the
|
||||
* callback for ptree_for_each_element_call_safe when bulk-freeing the tree. */
|
||||
static void free_synced_down_folder(synced_down_folder *f) {
|
||||
pmem_free(PMEM_SUBSYS_OTHER, f);
|
||||
}
|
||||
|
||||
static void psync_add_folder_to_downloadlist_locked(psync_folderid_t folderid) {
|
||||
synced_down_folder *f;
|
||||
if (!synced_down_folders) {
|
||||
|
|
@ -127,7 +133,7 @@ void psyncer_dl_queue_del(psync_folderid_t folderid) {
|
|||
|
||||
void psyncer_dl_queue_clear() {
|
||||
pthread_mutex_lock(&sync_down_mutex);
|
||||
ptree_for_each_element_call_safe(synced_down_folders, synced_down_folder, tree, free);
|
||||
ptree_for_each_element_call_safe(synced_down_folders, synced_down_folder, tree, free_synced_down_folder);
|
||||
synced_down_folders = PSYNC_TREE_EMPTY;
|
||||
pthread_mutex_unlock(&sync_down_mutex);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -192,7 +192,8 @@ int clib::pclsync_lib::receive_auth(const char *pass) {
|
|||
}
|
||||
|
||||
int clib::pclsync_lib::receive_auth_save(const char *pass) {
|
||||
psync_set_pass(pass, 1);
|
||||
const char *username = get_lib().username_.c_str();
|
||||
psync_set_user_pass(username, pass, 1);
|
||||
pthread_mutex_lock(&auth_mtx);
|
||||
get_lib().password_ = std::string(pass);
|
||||
pthread_cond_signal(&auth_cond);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,107 @@
|
|||
#!/bin/bash
|
||||
# Smoke test: large file read through FUSE mount (pcl-26j)
|
||||
#
|
||||
# Regression test for bad-free heap corruption in psync_pagecache_free_request.
|
||||
# A large read forces multiple psync_request_range_t allocations — one per
|
||||
# non-contiguous page range — which are then freed via free_request_range().
|
||||
# The old code called bare free() on pmem_malloc-allocated nodes; this build
|
||||
# uses AddressSanitizer to catch any such mismatched free.
|
||||
#
|
||||
# Usage:
|
||||
# PCLOUD_USER=you@example.com bash tests/smoke-tests/smoke-test-large-read.sh
|
||||
#
|
||||
# Requirements:
|
||||
# - PCLOUD_USER env var set (credentials prompted at daemon start)
|
||||
# - A file >= 50 MB present in the pCloud root (REMOTE_FILE below)
|
||||
# - FUSE available on the host
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
MOUNT="${HOME}/pCloudDrive"
|
||||
ASAN_LOG="/tmp/pcloudcc_asan_large_read_$$.log"
|
||||
REMOTE_FILE="${SMOKE_REMOTE_FILE:-}" # override via env if needed
|
||||
MIN_SIZE_MB=50
|
||||
|
||||
# ---- helpers ---------------------------------------------------------------
|
||||
|
||||
die() { echo "ERROR: $*" >&2; exit 1; }
|
||||
|
||||
cleanup() {
|
||||
echo "--- stopping daemon ---"
|
||||
echo "finalize" | ./pcloudcc -k 2>/dev/null || true
|
||||
sleep 1
|
||||
# Kill any stray daemon
|
||||
pkill -f "pcloudcc -d" 2>/dev/null || true
|
||||
echo "--- ASAN log ($ASAN_LOG) ---"
|
||||
cat "$ASAN_LOG" 2>/dev/null || echo "(empty)"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
# ---- sanity checks ---------------------------------------------------------
|
||||
|
||||
[[ -n "${PCLOUD_USER:-}" ]] || die "PCLOUD_USER not set"
|
||||
[[ -x ./pcloudcc ]] || die "pcloudcc binary not found — run make first"
|
||||
|
||||
# Verify the binary was built with ASAN
|
||||
if ! readelf -d ./pcloudcc 2>/dev/null | grep -q "libasan\|asan"; then
|
||||
echo "WARNING: pcloudcc does not appear to be an ASAN build."
|
||||
echo " For a meaningful test, rebuild with:"
|
||||
echo " make clean && CFLAGS='-fsanitize=address -g -O1' \\"
|
||||
echo " CXXFLAGS='-fsanitize=address -g -O1' \\"
|
||||
echo " LDFLAGS='-fsanitize=address' make -j\$(nproc)"
|
||||
echo " Continuing anyway — crash-level corruption may still surface."
|
||||
fi
|
||||
|
||||
# ---- build env -------------------------------------------------------------
|
||||
|
||||
export ASAN_OPTIONS="log_path=${ASAN_LOG}:abort_on_error=0:detect_leaks=0"
|
||||
|
||||
# ---- start daemon ----------------------------------------------------------
|
||||
|
||||
echo "--- starting pcloudcc daemon ---"
|
||||
./pcloudcc -u "$PCLOUD_USER" -d
|
||||
sleep 3 # wait for mount and initial sync
|
||||
|
||||
[[ -d "$MOUNT" ]] || die "mount point $MOUNT does not exist after daemon start"
|
||||
|
||||
# ---- find a large remote file ----------------------------------------------
|
||||
|
||||
if [[ -z "$REMOTE_FILE" ]]; then
|
||||
echo "--- searching for a file >= ${MIN_SIZE_MB} MB in ${MOUNT} ---"
|
||||
REMOTE_FILE=$(find "$MOUNT" -maxdepth 3 -type f \
|
||||
-size "+${MIN_SIZE_MB}M" -print -quit 2>/dev/null || true)
|
||||
fi
|
||||
|
||||
if [[ -z "$REMOTE_FILE" ]]; then
|
||||
echo "SKIP: no file >= ${MIN_SIZE_MB} MB found in ${MOUNT}."
|
||||
echo " Upload a large file first, or set SMOKE_REMOTE_FILE=/path/to/file."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "--- reading: $REMOTE_FILE ---"
|
||||
SIZE=$(stat -c%s "$REMOTE_FILE" 2>/dev/null || echo 0)
|
||||
echo " size: $((SIZE / 1024 / 1024)) MB"
|
||||
|
||||
# ---- read the file, exercising the page-cache request path -----------------
|
||||
|
||||
echo "--- streaming file to /dev/null ---"
|
||||
dd if="$REMOTE_FILE" of=/dev/null bs=1M 2>&1 | tail -1
|
||||
echo "--- read complete ---"
|
||||
|
||||
# Give ASAN a moment to flush any deferred reports
|
||||
sleep 1
|
||||
|
||||
# ---- evaluate ASAN output --------------------------------------------------
|
||||
|
||||
ASAN_ERRORS=$(grep -c "ERROR: AddressSanitizer" "$ASAN_LOG" 2>/dev/null || true)
|
||||
BAD_FREE_ERRORS=$(grep -c "attempting free on address" "$ASAN_LOG" 2>/dev/null || true)
|
||||
|
||||
echo ""
|
||||
if [[ "$ASAN_ERRORS" -eq 0 ]]; then
|
||||
echo "PASS: no AddressSanitizer errors detected during large file read"
|
||||
exit 0
|
||||
else
|
||||
echo "FAIL: $ASAN_ERRORS ASAN error(s) detected ($BAD_FREE_ERRORS bad-free)"
|
||||
echo " See $ASAN_LOG for details"
|
||||
exit 1
|
||||
fi
|
||||
|
|
@ -0,0 +1,361 @@
|
|||
/*
|
||||
* Test: bad-free detection for pmem_malloc-allocated tree/list nodes (pcl-26j)
|
||||
*
|
||||
* Verifies that the free helpers introduced in the pcl-26j fix call
|
||||
* pmem_free() — which backs up to the pmem_header_t before calling free() —
|
||||
* rather than bare free() directly on the data pointer.
|
||||
*
|
||||
* Each of the four fixed call sites is exercised:
|
||||
* 1. psync_request_range_t (ppagecache.c: psync_pagecache_free_request)
|
||||
* 2. synced_down_folder (psyncer.c: psyncer_dl_queue_clear)
|
||||
* 3. folder_tasks_t (ppathstatus.c: ppathstatus_init / sync_data_free)
|
||||
* 4. psync_sector_inlog_t (pfs.c: pfs_free_openfile)
|
||||
*
|
||||
* Mechanism
|
||||
* ---------
|
||||
* --wrap=malloc records every raw pointer that malloc() returns.
|
||||
* --wrap=free asserts that every pointer passed to free() is in that set.
|
||||
*
|
||||
* When the old code called bare free(data_ptr) on a pmem_malloc allocation,
|
||||
* data_ptr = hdr+1 (past the pmem_header_t). That pointer was never returned
|
||||
* by malloc, so the assertion would fire. With the fixed code, pmem_free()
|
||||
* backs up to hdr before calling free(hdr), which IS the malloc-returned
|
||||
* pointer. The assertion passes.
|
||||
*
|
||||
* Build:
|
||||
* gcc -fsanitize=address -g -o test_pcl26j_free \
|
||||
* tests/unit-tests/test_pcl26j_free.c pclsync/pmem.c pclsync/ptree.c \
|
||||
* pclsync/pdbg.c pclsync/putil.c pclsync/ppath.c tests/stubs/test_stubs.c \
|
||||
* -I./pclsync -Wl,--wrap=malloc -Wl,--wrap=free -lpthread
|
||||
*/
|
||||
|
||||
#define _POSIX_C_SOURCE 200809L
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#include "pmem.h"
|
||||
#include "ptree.h"
|
||||
#include "plist.h"
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* --wrap bookkeeping: record every malloc-returned pointer */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
#define MAX_ALLOCS 4096
|
||||
static void *g_alloc_ptrs[MAX_ALLOCS];
|
||||
static int g_alloc_count = 0;
|
||||
static int g_bad_frees = 0;
|
||||
|
||||
void *__real_malloc(size_t size);
|
||||
void __real_free(void *ptr);
|
||||
|
||||
void *__wrap_malloc(size_t size) {
|
||||
void *p = __real_malloc(size);
|
||||
if (p && g_alloc_count < MAX_ALLOCS)
|
||||
g_alloc_ptrs[g_alloc_count++] = p;
|
||||
return p;
|
||||
}
|
||||
|
||||
void __wrap_free(void *ptr) {
|
||||
if (!ptr) { __real_free(ptr); return; }
|
||||
for (int i = 0; i < g_alloc_count; i++) {
|
||||
if (g_alloc_ptrs[i] == ptr) {
|
||||
g_alloc_ptrs[i] = NULL; /* consume entry */
|
||||
__real_free(ptr);
|
||||
return;
|
||||
}
|
||||
}
|
||||
/* ptr was never returned by malloc — this is a bad free */
|
||||
fprintf(stderr, "BAD FREE: %p was not a malloc-returned pointer\n", ptr);
|
||||
g_bad_frees++;
|
||||
/* do NOT call free — avoid crashing so we can report all failures */
|
||||
}
|
||||
|
||||
static void reset_wrap_state(void) {
|
||||
memset(g_alloc_ptrs, 0, sizeof(g_alloc_ptrs));
|
||||
g_alloc_count = 0;
|
||||
g_bad_frees = 0;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Minimal struct replicas (mirrors of the production types) */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/* ppagecache.c */
|
||||
typedef struct {
|
||||
psync_list list;
|
||||
uint64_t offset;
|
||||
uint64_t length;
|
||||
} test_request_range_t;
|
||||
|
||||
typedef struct {
|
||||
psync_list ranges;
|
||||
} test_request_t;
|
||||
|
||||
/* psyncer.c */
|
||||
typedef struct {
|
||||
psync_tree tree;
|
||||
unsigned long long folderid;
|
||||
} test_synced_down_folder_t;
|
||||
|
||||
/* ppathstatus.c */
|
||||
typedef struct {
|
||||
psync_tree tree;
|
||||
unsigned long long folderid;
|
||||
int child_task_cnt;
|
||||
int own_tasks;
|
||||
} test_folder_tasks_t;
|
||||
|
||||
/* pfs.c / pfscrypto.c */
|
||||
typedef struct {
|
||||
psync_tree tree;
|
||||
uint32_t sectorid;
|
||||
uint32_t logoffset;
|
||||
} test_sector_inlog_t;
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Free helpers — exact copies of the production fix */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static void free_request_range(test_request_range_t *r) {
|
||||
pmem_free(PMEM_SUBSYS_CACHE, r);
|
||||
}
|
||||
|
||||
static void free_synced_down_folder(test_synced_down_folder_t *f) {
|
||||
pmem_free(PMEM_SUBSYS_OTHER, f);
|
||||
}
|
||||
|
||||
static void free_folder_tasks_node(test_folder_tasks_t *ft) {
|
||||
pmem_free(PMEM_SUBSYS_OTHER, ft);
|
||||
}
|
||||
|
||||
static void free_sector_inlog_node(test_sector_inlog_t *e) {
|
||||
pmem_free(PMEM_SUBSYS_OTHER, e);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Test harness */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static int passes = 0, failures = 0;
|
||||
|
||||
#define PASS(n) do { printf("PASS: %s\n", n); passes++; } while (0)
|
||||
#define FAIL(n, ...) do { printf("FAIL: %s — ", n); \
|
||||
printf(__VA_ARGS__); printf("\n"); failures++; } while (0)
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Test 1: psync_request_range_t list freed via free_request_range() */
|
||||
/* ------------------------------------------------------------------ */
|
||||
static void test_request_range_free(void) {
|
||||
reset_wrap_state();
|
||||
|
||||
test_request_t req;
|
||||
psync_list_init(&req.ranges);
|
||||
|
||||
/* Allocate 5 range nodes (simulates a large multi-range read) */
|
||||
for (int i = 0; i < 5; i++) {
|
||||
test_request_range_t *r = pmem_malloc(PMEM_SUBSYS_CACHE,
|
||||
sizeof(test_request_range_t));
|
||||
r->offset = (uint64_t)i * 4096;
|
||||
r->length = 4096;
|
||||
psync_list_add_tail(&req.ranges, &r->list);
|
||||
}
|
||||
|
||||
/* Exercise the fixed free path */
|
||||
psync_list_for_each_element_call(&req.ranges, test_request_range_t,
|
||||
list, free_request_range);
|
||||
|
||||
if (g_bad_frees == 0)
|
||||
PASS("request_range: pmem_free called with header ptr, not data ptr");
|
||||
else
|
||||
FAIL("request_range", "%d bad free(s) detected", g_bad_frees);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Test 2: synced_down_folder tree freed via free_synced_down_folder() */
|
||||
/* ------------------------------------------------------------------ */
|
||||
static void test_synced_down_folder_free(void) {
|
||||
reset_wrap_state();
|
||||
|
||||
psync_tree *root = PSYNC_TREE_EMPTY;
|
||||
|
||||
/* Build a small BST of synced_down_folder nodes */
|
||||
unsigned long long fids[] = {10, 5, 15, 3, 7};
|
||||
for (int i = 0; i < 5; i++) {
|
||||
test_synced_down_folder_t *f = pmem_malloc(PMEM_SUBSYS_OTHER,
|
||||
sizeof(test_synced_down_folder_t));
|
||||
f->folderid = fids[i];
|
||||
memset(&f->tree, 0, sizeof(f->tree));
|
||||
|
||||
if (!root) {
|
||||
ptree_add_after(&root, NULL, &f->tree);
|
||||
} else {
|
||||
/* Simple insertion — walk tree */
|
||||
psync_tree *cur = root, **slot = NULL;
|
||||
psync_tree *parent = NULL;
|
||||
while (cur) {
|
||||
test_synced_down_folder_t *n =
|
||||
ptree_element(cur, test_synced_down_folder_t, tree);
|
||||
parent = cur;
|
||||
if (f->folderid < n->folderid) {
|
||||
if (!cur->left) { slot = &cur->left; break; }
|
||||
cur = cur->left;
|
||||
} else {
|
||||
if (!cur->right) { slot = &cur->right; break; }
|
||||
cur = cur->right;
|
||||
}
|
||||
}
|
||||
if (slot) {
|
||||
*slot = &f->tree;
|
||||
ptree_added_at(&root, parent, &f->tree);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Exercise the fixed free path */
|
||||
ptree_for_each_element_call_safe(root, test_synced_down_folder_t,
|
||||
tree, free_synced_down_folder);
|
||||
|
||||
if (g_bad_frees == 0)
|
||||
PASS("synced_down_folder: pmem_free called with header ptr, not data ptr");
|
||||
else
|
||||
FAIL("synced_down_folder", "%d bad free(s) detected", g_bad_frees);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Test 3: folder_tasks_t tree freed via free_folder_tasks_node() */
|
||||
/* ------------------------------------------------------------------ */
|
||||
static void test_folder_tasks_free(void) {
|
||||
reset_wrap_state();
|
||||
|
||||
psync_tree *root = PSYNC_TREE_EMPTY;
|
||||
|
||||
/* Allocate 6 folder_tasks nodes */
|
||||
unsigned long long fids[] = {100, 50, 150, 25, 75, 125};
|
||||
for (int i = 0; i < 6; i++) {
|
||||
test_folder_tasks_t *ft = pmem_malloc(PMEM_SUBSYS_OTHER,
|
||||
sizeof(test_folder_tasks_t));
|
||||
ft->folderid = fids[i];
|
||||
ft->child_task_cnt = 0;
|
||||
ft->own_tasks = 0;
|
||||
memset(&ft->tree, 0, sizeof(ft->tree));
|
||||
|
||||
if (!root) {
|
||||
ptree_add_after(&root, NULL, &ft->tree);
|
||||
} else {
|
||||
psync_tree *cur = root, **slot = NULL;
|
||||
psync_tree *parent = NULL;
|
||||
while (cur) {
|
||||
test_folder_tasks_t *n =
|
||||
ptree_element(cur, test_folder_tasks_t, tree);
|
||||
parent = cur;
|
||||
if (ft->folderid < n->folderid) {
|
||||
if (!cur->left) { slot = &cur->left; break; }
|
||||
cur = cur->left;
|
||||
} else {
|
||||
if (!cur->right) { slot = &cur->right; break; }
|
||||
cur = cur->right;
|
||||
}
|
||||
}
|
||||
if (slot) {
|
||||
*slot = &ft->tree;
|
||||
ptree_added_at(&root, parent, &ft->tree);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Exercise the fixed free path */
|
||||
ptree_for_each_element_call_safe(root, test_folder_tasks_t,
|
||||
tree, free_folder_tasks_node);
|
||||
|
||||
if (g_bad_frees == 0)
|
||||
PASS("folder_tasks: pmem_free called with header ptr, not data ptr");
|
||||
else
|
||||
FAIL("folder_tasks", "%d bad free(s) detected", g_bad_frees);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Test 4: psync_sector_inlog_t tree freed via free_sector_inlog_node() */
|
||||
/* ------------------------------------------------------------------ */
|
||||
static void test_sector_inlog_free(void) {
|
||||
reset_wrap_state();
|
||||
|
||||
psync_tree *root = PSYNC_TREE_EMPTY;
|
||||
|
||||
/* Allocate 4 sector_inlog nodes */
|
||||
uint32_t sids[] = {0, 1, 2, 3};
|
||||
for (int i = 0; i < 4; i++) {
|
||||
test_sector_inlog_t *e = pmem_malloc(PMEM_SUBSYS_OTHER,
|
||||
sizeof(test_sector_inlog_t));
|
||||
e->sectorid = sids[i];
|
||||
e->logoffset = (uint32_t)(i * 512);
|
||||
memset(&e->tree, 0, sizeof(e->tree));
|
||||
|
||||
if (!root) {
|
||||
ptree_add_after(&root, NULL, &e->tree);
|
||||
} else {
|
||||
psync_tree *cur = root, **slot = NULL;
|
||||
psync_tree *parent = NULL;
|
||||
while (cur) {
|
||||
test_sector_inlog_t *n =
|
||||
ptree_element(cur, test_sector_inlog_t, tree);
|
||||
parent = cur;
|
||||
if (e->sectorid < n->sectorid) {
|
||||
if (!cur->left) { slot = &cur->left; break; }
|
||||
cur = cur->left;
|
||||
} else {
|
||||
if (!cur->right) { slot = &cur->right; break; }
|
||||
cur = cur->right;
|
||||
}
|
||||
}
|
||||
if (slot) {
|
||||
*slot = &e->tree;
|
||||
ptree_added_at(&root, parent, &e->tree);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Exercise the fixed free path */
|
||||
ptree_for_each_element_call_safe(root, test_sector_inlog_t,
|
||||
tree, free_sector_inlog_node);
|
||||
|
||||
if (g_bad_frees == 0)
|
||||
PASS("sector_inlog: pmem_free called with header ptr, not data ptr");
|
||||
else
|
||||
FAIL("sector_inlog", "%d bad free(s) detected", g_bad_frees);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Test 5: bare free() on pmem_malloc pointer IS detected as bad-free */
|
||||
/* (verifies that the wrap harness itself is working) */
|
||||
/* ------------------------------------------------------------------ */
|
||||
static void test_harness_detects_bad_free(void) {
|
||||
reset_wrap_state();
|
||||
|
||||
void *data = pmem_malloc(PMEM_SUBSYS_OTHER, 64);
|
||||
/* Deliberately call bare free on the data pointer — should be caught */
|
||||
free(data);
|
||||
|
||||
if (g_bad_frees == 1)
|
||||
PASS("harness self-check: bare free(data_ptr) correctly flagged");
|
||||
else
|
||||
FAIL("harness self-check", "expected 1 bad free, got %d", g_bad_frees);
|
||||
|
||||
/* Reset so leak-sanitizer doesn't complain about the unfree'd block */
|
||||
reset_wrap_state();
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
int main(void) {
|
||||
test_request_range_free();
|
||||
test_synced_down_folder_free();
|
||||
test_folder_tasks_free();
|
||||
test_sector_inlog_free();
|
||||
test_harness_detects_bad_free();
|
||||
|
||||
printf("\n%d passed, %d failed\n", passes, failures);
|
||||
return failures ? 1 : 0;
|
||||
}
|
||||
Loading…
Reference in New Issue