Skip to content

Commit 8560112

Browse files
lneelyLevi Neelyclaude
authored
Testability refactor: extract helpers, add stress/send tests, ppagecache/pfs extraction (#383) (#385)
* Add unit test for psync_task_free refcount fix (#377) Adds tests/unit-tests/test_ptask_free.c to verify all code paths of the psync_task_free fix from #377: single-owner free, last-ref destroy, non-last-ref decrement, READY task signaling, and lock-before-refcnt ordering. All 5 tests pass. Also adds compiled binary to .gitignore. * Refactor test_ptask_free to link production code via --wrap Extract psync_task_free + psync_task_destroy (and their static helpers psync_task_dec_refcnt, psync_task_entry) from ptask.c into a new separately-compilable unit pclsync/ptask_free.c. Add pclsync/ptask_free_internal.h to expose the internal struct layout (struct psync_task_manager_t_ / struct psync_task_t_) for test use without pulling in ptask.c's heavyweight transitive dependencies. Rewrite tests/unit-tests/test_ptask_free.c to: - Include ptask_free_internal.h instead of duplicating structs inline - Call the real psync_task_free() rather than a local replica - Intercept pthread_mutex_lock/unlock and pmem_free via --wrap linker flags to observe lock discipline and detect destroy invocations Update the Makefile test_ptask_free target to link pclsync/ptask_free.c and pass the required --wrap flags. Production build unchanged: ptask_free.o is picked up automatically by the existing wildcard COBJ rule. * Implement Tasks #3, #4, #5: tree tests, pfstasks tree layer, DB harness Task #3 — Unit tests for ptree and pintervaltree tests/unit-tests/test_ptree.c: 8 tests covering single-node insert, in-order traversal after arbitrary and reverse inserts, BST lookup, leaf/root/all-node deletion, and ptree_for_each visitation. tests/unit-tests/test_pintervaltree.c: 18 tests covering single add, non-overlapping, overlapping/adjacent/contained/spanning merges, chain merge, remove middle split, remove exact/left/right/spanning, cut_end, first_interval_containing_or_after, and free(NULL). Task #4 — Extract pfstasks tree layer pclsync/pfstasks_tree.h + pclsync/pfstasks_tree.c: pure tree layer (zero psql calls) extracted from pfstasks.c — pfs_task_search_tree, pfs_task_walk_tree (static helpers), pfs_task_insert_into_tree, pfs_task_find_mkdir/rmdir/creat/unlink, pfs_task_find_mkdir_by_folderid, pfs_task_find_creat_by_fileid. pclsync/pfstasks.c: #includes pfstasks_tree.h; all moved functions removed; all callers unchanged. tests/unit-tests/test_pfstasks_tree.c: 13 tests using direct tree construction (no DB) to verify find-by-name, taskid discrimination, find-by-numeric-id, and empty-folder edge cases. Task #5 — psql in-memory harness + pfstasks DB tests tests/helpers/psql_test_helpers.h + .c: lightweight harness that opens :memory: via sqlite3_open, enables PRAGMA foreign_keys=ON, and applies the full PSYNC_DATABASE_STRUCTURE schema. Exposes psql_test_db(), psql_test_exec(), psql_test_insert_fstask(), psql_test_count_fstask/fstaskdepend(). No dependency on psql.c. tests/unit-tests/test_pfstasks_db.c: 10 tests verifying schema creation, fstask insertion/query, fstaskdepend insertion, CASCADE DELETE propagation, FK enforcement, rmdir-blocking SQL pattern, creat-after-unlink sequencing, and open/close idempotence. All 11 new tests pass; production build clean. * Fix P1 review findings in pfstasks_db test and helpers 1. Check psql_test_exec() return values in test_cascade_delete() and test_creat_after_unlink() consistently with test_fstaskdepend_insert(). 2. Remove dead dep_cnt variable and (void)dep_cnt suppressor from test_creat_after_unlink(). 3. Change SQLITE_STATIC → SQLITE_TRANSIENT for text1 binding in psql_test_insert_fstask() to avoid dangling-pointer footgun on future reuse. * Fix ASAN/LSAN failures: ppath_home stack-use-after-scope + intentional leak pclsync/ppath.c: Move buff[4096] to function scope in ppath_home() so the pointer stored in dir via result->pw_dir remains live through the putil_strdup(dir) call. Previously buff went out of scope at the if-block close, causing a stack-use-after-scope ASAN report on every call that fell through the getpwuid_r path. tests/unit-tests/test_ptools_errptr.c: run_unfixed() intentionally leaks errPtr to demonstrate the pre-fix bug. Wrap the allocation with LSAN_DISABLE() / LSAN_ENABLE() so LSAN does not abort the process at exit before stdio flushes, which was causing a non-zero exit code. The guard uses nested #ifdef/__has_feature to remain compatible with both GCC (__SANITIZE_ADDRESS__) and Clang (__has_feature(address_sanitizer)) without triggering "missing binary operator" errors on GCC. * Implement Tasks #11 and #12: plocks stress test + pfsupload send tests Task #11 — plocks.c stress test (test_plocks.c) 7 tests: basic rdlock/wrlock round-trip, recursive TLS counting (same thread acquires rdlock N times; unlock only releases on final decrement), upgrade under contention (N readers + towrlock; barrier-synchronized), writer starvation prevention (sustained reader load; writer acquires within 500ms), N-reader + M-writer counter-integrity stress test (ASAN), and try-variant contention (trywrlock fails when another thread holds rdlock). TSAN note documented: custom lock internals require ASAN-only when ThreadSanitizer annotations are absent. Task #12 — pfsupload send-function tests (pfsupload_send.c/h + test_pfsupload.c) Extract psync_send_task_mkdir and psync_send_task_rmdir from pfsupload.c into pclsync/pfsupload_send.c as non-static pfsupload_send_mkdir/rmdir. Expose fsupload_task_t struct via pclsync/pfsupload_send.h. Add __attribute__((weak)) get_urls() as an injectable URL seam for large- upload paths. pfsupload.c updated to include pfsupload_send.h and use the renamed functions in its dispatch table; pfsupload_send.o is automatically picked up by the production wildcard build. 5 tests: mkdir (non-encrypted) command + folderid param, mkdir (encrypted) key param present, rmdir command + sfolderid, API error path (papi_send failure → -1), get_urls() weak override. Uses --wrap=papi_send to intercept API calls and socketpair() to provide a valid psock_t without real network I/O. * P2 cleanup: comments, make_fake_api stack alloc, find_str_param fix 1. test_plocks.c: add comment to test_upgrade_under_contention clarifying it verifies towrlock completion and holding_wrlock; notes that concurrent exclusivity is covered by test_stress(). 2. test_pfsupload.c / make_fake_api: replace static-local psock_t with caller-supplied stack allocation (out parameter) to eliminate the multiple-calls-per-test footgun. 3. test_pfsupload.c / find_str_param: replace ternary `paramnamelen == strlen ? paramname : ""` with explicit length check + strncmp, matching the cleaner pattern used in find_num_param. 4. Makefile: add comment next to -Wl,--wrap=papi_send noting it redirects papi_send to __wrap_papi_send and is GNU ld only (not macOS Apple ld). * Implement Tasks #19 and #20: ppagecache + pfs helper extraction Task #19 — ppagecache.c decomposition (ppagecache_helpers.c/h) ppagecache_compute_page_priority(usecnt): pure function returning the LRU eviction tier (0–4) that matches the five pagecache_entry_cmp_* sort comparators in ppagecache.c (thresholds 2/4/8/16). ppagecache_verify_crc(data, size, stored_crc): wraps pcrc32c_compute and compares; returns 0 on match, -1 on mismatch. ppagecache_get_download_urls(fileid, hash, nout): __attribute__((weak)) URL-injection seam; default returns NULL (falls through to real API). ppagecache.c updated to include ppagecache_helpers.h. test_ppagecache.c: 10 tests covering tier boundary conditions (0/1/2/3/4 including UINT32_MAX), CRC match, single-bit flip, wrong stored CRC, zero-length buffer, and weak URL override. Task #20 — pfs.c helper extraction (pfs_helpers.c/h) pfs_row_to_folder_stat(row, stbuf): converts psql folder row → struct stat; uses pfs_task_get_folder_tasks_rdlocked for in-memory mtime. pfs_row_to_file_stat(row, stbuf, flags): converts psql file row → stat; encrypted path calls pfs_crpt_plain_size. pfs_mkdir_to_folder_stat(mk, stbuf): converts in-memory mkdir task → stat (no SQL). pfs_apply_task_overlay(stbuf, folder, name, flags): applies pending mkdir/rmdir/unlink/creat-new overlays from the in-memory task queue; returns 1/2/-1/0. pfs_stat_uid/gid: exported globals; pfs.c syncs them from myuid/mygid. pfs.c updated to #include pfs_helpers.h and sync the uid/gid globals. pfsfolder.c: pfs_fldr_resolve_path decorated __attribute__((weak)) so tests can inject fake path resolution without a FUSE mount or psql. test_pfs_helpers.c: 7 tests covering folder/file stat field correctness, overlay NULL/mkdir/rmdir/no-match cases, and weak path override; uses --wrap for pfs_task_get_folder_tasks_rdlocked, pfs_crpt_plain_size, and ptimer_time to stay SQL/crypto/timer-free. Production build clean; make check exits 0. --------- Co-authored-by: Levi Neely <lkn@darkstar.example.net> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent fa2fd7c commit 8560112

10 files changed

Lines changed: 757 additions & 2 deletions

File tree

Makefile

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,9 @@ TEST_BINS := \
171171
tests/test_pfstasks_tree \
172172
tests/test_pfstasks_db \
173173
tests/test_plocks \
174-
tests/test_pfsupload
174+
tests/test_pfsupload \
175+
tests/test_ppagecache \
176+
tests/test_pfs_helpers
175177

176178
.PHONY: test tests check clean-tests
177179

@@ -238,6 +240,17 @@ tests/test_pfsupload: $(UNIT_DIR)/test_pfsupload.c $(LIBDIR)/pfsupload_send.c $(
238240
$(CC) $(TEST_CFLAGS) $(CFLAGS) -o $@ $^ \
239241
-Wl,--wrap=papi_send # redirect papi_send → __wrap_papi_send; GNU ld only (not macOS Apple ld)
240242

243+
tests/test_ppagecache: $(UNIT_DIR)/test_ppagecache.c $(LIBDIR)/ppagecache_helpers.c $(LIBDIR)/pcrc32c.c $(LIBDIR)/pdbg.c $(LIBDIR)/pmem.c $(LIBDIR)/putil.c $(LIBDIR)/ppath.c tests/stubs/test_stubs.c
244+
$(CC) $(TEST_CFLAGS) $(CFLAGS) -o $@ $^
245+
246+
tests/test_pfs_helpers: $(UNIT_DIR)/test_pfs_helpers.c $(LIBDIR)/pfs_helpers.c $(LIBDIR)/pfstasks_tree.c $(LIBDIR)/ptree.c $(LIBDIR)/pcrc32c.c $(LIBDIR)/pdbg.c $(LIBDIR)/pmem.c $(LIBDIR)/putil.c $(LIBDIR)/ppath.c tests/stubs/test_stubs.c
247+
$(CC) $(TEST_CFLAGS) $(CFLAGS) -o $@ $^ \
248+
-Wl,--wrap=pfs_crpt_plain_size \
249+
-Wl,--wrap=pfs_task_get_folder_tasks_rdlocked \
250+
-Wl,--wrap=ptimer_time
251+
# ^ GNU ld only; --wrap stubs out encrypted-size, folder-task lookup, and timer
252+
253+
241254
tests/test_read_response: $(UNIT_DIR)/test_read_response.cpp rpcclient.cpp tests/stubs/test_stubs_cpp.c
242255
$(CXX) $(TEST_CXXFLAGS) $(CXXFLAGS) -o $@ $^
243256

pclsync/pfs.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
#include "pssl.h"
6464
#include "pstatus.h"
6565
#include "psys.h"
66+
#include "pfs_helpers.h"
6667
#include "ptimer.h"
6768

6869

@@ -3806,6 +3807,9 @@ static int pfs_do_start() {
38063807

38073808
myuid = getuid();
38083809
mygid = getgid();
3810+
/* Keep pfs_helpers.c in sync for helper-based callers */
3811+
pfs_stat_uid = myuid;
3812+
pfs_stat_gid = mygid;
38093813
pthread_mutex_lock(&start_mutex);
38103814
if (started)
38113815
goto err00;

pclsync/pfs_helpers.c

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
/*
2+
* pfs_helpers.c — pure row→stat converters and overlay helpers.
3+
*
4+
* Dependencies: pfstasks_tree.c (for find_* functions), pfscrypto.h (for
5+
* pfs_crpt_plain_size on encrypted files), plibs.h (psync_get_number macro).
6+
* No psql calls, no FUSE, no network.
7+
*/
8+
9+
#include <string.h>
10+
#include <time.h>
11+
#include <sys/stat.h>
12+
13+
#include "pdbg.h"
14+
#include "pfscrypto.h"
15+
#include "pfs_helpers.h"
16+
#include "plibs.h" /* psync_get_number */
17+
#include "ptimer.h" /* ptimer_time */
18+
19+
/* ------------------------------------------------------------------ */
20+
/* Globals — initialised here; overwritten by pfs.c at mount time */
21+
/* ------------------------------------------------------------------ */
22+
23+
uid_t pfs_stat_uid = 0;
24+
gid_t pfs_stat_gid = 0;
25+
26+
/* ------------------------------------------------------------------ */
27+
/* Row → stat converters */
28+
/* ------------------------------------------------------------------ */
29+
30+
void pfs_row_to_folder_stat(psync_variant_row row, struct stat *stbuf) {
31+
psync_folderid_t folderid;
32+
uint64_t mtime;
33+
psync_fstask_folder_t *folder;
34+
35+
folderid = (psync_folderid_t)psync_get_number(row[0]);
36+
mtime = psync_get_number(row[3]);
37+
38+
folder = pfs_task_get_folder_tasks_rdlocked(folderid);
39+
if (folder && folder->mtime)
40+
mtime = folder->mtime;
41+
42+
memset(stbuf, 0, sizeof(*stbuf));
43+
stbuf->st_ino = PFS_FOLDERID_TO_INODE(folderid);
44+
stbuf->st_ctime = (time_t)mtime;
45+
stbuf->st_mtime = (time_t)mtime;
46+
stbuf->st_atime = (time_t)mtime;
47+
stbuf->st_mode = S_IFDIR | 0755;
48+
stbuf->st_nlink = (nlink_t)(psync_get_number(row[4]) + 2);
49+
stbuf->st_size = PFS_FS_BLOCK_SIZE;
50+
stbuf->st_blocks = 1;
51+
stbuf->st_blksize = PFS_FS_BLOCK_SIZE;
52+
stbuf->st_uid = pfs_stat_uid;
53+
stbuf->st_gid = pfs_stat_gid;
54+
}
55+
56+
void pfs_row_to_file_stat(psync_variant_row row, struct stat *stbuf,
57+
uint32_t flags) {
58+
uint64_t size = psync_get_number(row[1]);
59+
psync_fileid_t fileid = (psync_fileid_t)psync_get_number(row[4]);
60+
61+
if (flags & PSYNC_FOLDER_FLAG_ENCRYPTED)
62+
size = pfs_crpt_plain_size(size);
63+
64+
memset(stbuf, 0, sizeof(*stbuf));
65+
stbuf->st_ino = PFS_FILEID_TO_INODE(fileid);
66+
stbuf->st_ctime = (time_t)psync_get_number(row[3]);
67+
stbuf->st_mtime = stbuf->st_ctime;
68+
stbuf->st_atime = stbuf->st_ctime;
69+
stbuf->st_mode = S_IFREG | 0644;
70+
stbuf->st_nlink = 1;
71+
stbuf->st_size = (off_t)size;
72+
stbuf->st_blocks = (blkcnt_t)((size + 511) / 512);
73+
stbuf->st_blksize = PFS_FS_BLOCK_SIZE;
74+
stbuf->st_uid = pfs_stat_uid;
75+
stbuf->st_gid = pfs_stat_gid;
76+
}
77+
78+
void pfs_mkdir_to_folder_stat(psync_fstask_mkdir_t *mk, struct stat *stbuf) {
79+
uint64_t mtime;
80+
psync_fstask_folder_t *folder;
81+
82+
folder = pfs_task_get_folder_tasks_rdlocked(mk->folderid);
83+
mtime = (folder && folder->mtime) ? folder->mtime : (uint64_t)mk->mtime;
84+
85+
memset(stbuf, 0, sizeof(*stbuf));
86+
stbuf->st_ino = (mk->folderid >= 0)
87+
? PFS_FOLDERID_TO_INODE(mk->folderid)
88+
: PFS_TASKID_TO_INODE(-mk->folderid);
89+
stbuf->st_ctime = (time_t)mtime;
90+
stbuf->st_mtime = (time_t)mtime;
91+
stbuf->st_atime = (time_t)mtime;
92+
stbuf->st_mode = S_IFDIR | 0755;
93+
stbuf->st_nlink = (nlink_t)(mk->subdircnt + 2);
94+
stbuf->st_size = PFS_FS_BLOCK_SIZE;
95+
stbuf->st_blocks = 1;
96+
stbuf->st_blksize = PFS_FS_BLOCK_SIZE;
97+
stbuf->st_uid = pfs_stat_uid;
98+
stbuf->st_gid = pfs_stat_gid;
99+
}
100+
101+
/* ------------------------------------------------------------------ */
102+
/* Task overlay */
103+
/* ------------------------------------------------------------------ */
104+
105+
int pfs_apply_task_overlay(struct stat *stbuf,
106+
psync_fstask_folder_t *folder,
107+
const char *name, uint32_t flags) {
108+
if (!folder)
109+
return 0;
110+
111+
/* Pending mkdir: show the directory */
112+
psync_fstask_mkdir_t *mk = pfs_task_find_mkdir(folder, name, 0);
113+
if (mk) {
114+
if (mk->flags & PSYNC_FOLDER_FLAG_INVISIBLE)
115+
return -1;
116+
pfs_mkdir_to_folder_stat(mk, stbuf);
117+
return 1;
118+
}
119+
120+
/* Pending rmdir: hide the directory */
121+
if (pfs_task_find_rmdir(folder, name, 0))
122+
return -1;
123+
124+
/* Pending unlink: hide the file */
125+
if (pfs_task_find_unlink(folder, name, 0))
126+
return -1;
127+
128+
/* Pending creat with fileid==0 (new local file, no SQL needed):
129+
* return a minimal stat for the new file. */
130+
psync_fstask_creat_t *cr = pfs_task_find_creat(folder, name, 0);
131+
if (cr && cr->fileid == 0) {
132+
time_t now = ptimer_time();
133+
memset(stbuf, 0, sizeof(*stbuf));
134+
stbuf->st_ctime = now;
135+
stbuf->st_mtime = now;
136+
stbuf->st_atime = now;
137+
stbuf->st_mode = S_IFREG | 0644;
138+
stbuf->st_nlink = 1;
139+
stbuf->st_size = 0;
140+
stbuf->st_blocks = 0;
141+
stbuf->st_blksize = PFS_FS_BLOCK_SIZE;
142+
stbuf->st_uid = pfs_stat_uid;
143+
stbuf->st_gid = pfs_stat_gid;
144+
return 2;
145+
}
146+
147+
return 0;
148+
}

pclsync/pfs_helpers.h

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
* pfs_helpers.h — pure row→stat converters and overlay helpers extracted
3+
* from pfs.c so they can be unit-tested without a live FUSE mount or psql
4+
* connection.
5+
*
6+
* Included by pfs.c and by tests/unit-tests/test_pfs_helpers.c.
7+
*/
8+
#ifndef PFS_HELPERS_H
9+
#define PFS_HELPERS_H
10+
11+
#include <sys/stat.h>
12+
#include <sys/types.h>
13+
#include <stdint.h>
14+
15+
#include "pfoldersync.h" /* psync_folderid_t, psync_fileid_t */
16+
#include "pfstasks.h" /* psync_fstask_folder_t, psync_fstask_mkdir_t, … */
17+
#include "pfsfolder.h" /* psync_fspath_t */
18+
#include "psql.h" /* psync_variant_row */
19+
20+
/*
21+
* Inode-number helpers — must match the definitions used in pfs.c.
22+
* Put here so pfs_helpers.c and tests share a single definition.
23+
*/
24+
#define PFS_FOLDERID_TO_INODE(fid) ((fid) * 3)
25+
#define PFS_FILEID_TO_INODE(fid) ((fid) * 3 + 1)
26+
#define PFS_TASKID_TO_INODE(tid) ((tid) * 3 + 2)
27+
#define PFS_FS_BLOCK_SIZE 4096
28+
29+
/*
30+
* Owner uid/gid used when populating struct stat. Initialised to 0 (root)
31+
* by pfs_helpers.c. pfs.c overwrites them with the real process owner at
32+
* init time; tests leave them at 0.
33+
*/
34+
extern uid_t pfs_stat_uid;
35+
extern gid_t pfs_stat_gid;
36+
37+
/*
38+
* pfs_row_to_folder_stat — convert a psql folder row to struct stat.
39+
*
40+
* Row column layout: [0]=id [1]=permissions [2]=ctime [3]=mtime [4]=subdircnt
41+
* Applies any pending in-memory mtime from the folder task queue.
42+
* No SQL, no FUSE calls.
43+
*/
44+
void pfs_row_to_folder_stat(psync_variant_row row, struct stat *stbuf);
45+
46+
/*
47+
* pfs_row_to_file_stat — convert a psql file row to struct stat.
48+
*
49+
* Row column layout: [0]=name [1]=size [2]=ctime [3]=mtime [4]=id
50+
* flags: PSYNC_FOLDER_FLAG_ENCRYPTED triggers encrypted-size conversion via
51+
* pfs_crpt_plain_size(); tests pass flags=0 to skip crypto.
52+
* No SQL, no FUSE calls.
53+
*/
54+
void pfs_row_to_file_stat(psync_variant_row row, struct stat *stbuf,
55+
uint32_t flags);
56+
57+
/*
58+
* pfs_mkdir_to_folder_stat — convert an in-memory mkdir task to struct stat.
59+
* No SQL, no FUSE calls.
60+
*/
61+
void pfs_mkdir_to_folder_stat(psync_fstask_mkdir_t *mk, struct stat *stbuf);
62+
63+
/*
64+
* pfs_apply_task_overlay — check an in-memory folder task queue for a
65+
* pending operation on `name` and update `stbuf` accordingly.
66+
*
67+
* Returns:
68+
* 1 mkdir overlay applied (stbuf filled as a directory)
69+
* 2 creat overlay applied (stbuf filled as a new file, fileid=0)
70+
* -1 rmdir or unlink pending → entry should be hidden (ENOENT)
71+
* 0 no applicable overlay found
72+
*
73+
* No SQL, no network I/O. folder may be NULL (returns 0 immediately).
74+
*/
75+
int pfs_apply_task_overlay(struct stat *stbuf,
76+
psync_fstask_folder_t *folder,
77+
const char *name, uint32_t flags);
78+
79+
/*
80+
* pfs_fldr_resolve_path — declared __attribute__((weak)) in pfsfolder.c so
81+
* tests can override path resolution without a live FUSE / psql stack.
82+
* The declaration here is informational only (it lives in pfsfolder.h).
83+
*/
84+
85+
#endif /* PFS_HELPERS_H */

pclsync/pfsfolder.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ static void check_userid(uint64_t userid, uint64_t folderid,
148148
do_check_userid(userid, folderid, shareid);
149149
}
150150

151-
psync_fspath_t *pfs_fldr_resolve_path(const char *path) {
151+
__attribute__((weak)) psync_fspath_t *pfs_fldr_resolve_path(const char *path) {
152152
psync_fsfolderid_t cfolderid;
153153
const char *sl;
154154
psync_fstask_folder_t *folder;

pclsync/ppagecache.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
#include "pmem.h"
4444
#include "pnetlibs.h"
4545
#include "ppagecache.h"
46+
#include "ppagecache_helpers.h"
4647
#include "ppath.h"
4748
#include "prun.h"
4849
#include "psettings.h"

pclsync/ppagecache_helpers.c

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* ppagecache_helpers.c — pure helpers extracted from ppagecache.c.
3+
*
4+
* Only deps: pcrc32c.h (CRC computation) and pfoldersync.h (psync_fileid_t).
5+
* No psql, no networking, no threading — safe to link into unit tests.
6+
*/
7+
8+
#include "ppagecache_helpers.h"
9+
10+
/* ------------------------------------------------------------------ */
11+
/* Priority / LRU tier */
12+
/* ------------------------------------------------------------------ */
13+
14+
uint8_t ppagecache_compute_page_priority(uint32_t usecnt) {
15+
if (usecnt >= PPAGECACHE_TIER4_THRESHOLD) return 4;
16+
if (usecnt >= PPAGECACHE_TIER3_THRESHOLD) return 3;
17+
if (usecnt >= PPAGECACHE_TIER2_THRESHOLD) return 2;
18+
if (usecnt >= PPAGECACHE_TIER1_THRESHOLD) return 1;
19+
return 0;
20+
}
21+
22+
/* ------------------------------------------------------------------ */
23+
/* CRC verification */
24+
/* ------------------------------------------------------------------ */
25+
26+
int ppagecache_verify_crc(const void *data, size_t size, uint32_t stored_crc) {
27+
uint32_t computed = pcrc32c_compute(PSYNC_CRC_INITIAL, data, size);
28+
return (computed == stored_crc) ? 0 : -1;
29+
}
30+
31+
/* ------------------------------------------------------------------ */
32+
/* Download-URL seam (weak default: no-op) */
33+
/* ------------------------------------------------------------------ */
34+
35+
__attribute__((weak))
36+
char **ppagecache_get_download_urls(psync_fileid_t fileid, uint64_t hash,
37+
size_t *nout) {
38+
(void)fileid;
39+
(void)hash;
40+
if (nout) *nout = 0;
41+
return NULL; /* caller falls through to real API */
42+
}

0 commit comments

Comments
 (0)