From 561d7156e246ada3ff372f1299e798f354f40364 Mon Sep 17 00:00:00 2001 From: servicememeai Date: Tue, 20 Oct 2026 15:03:36 +0900 Subject: [PATCH 01/13] fix(storage): route all C commit/fs operations through the V2 authority [skip ci] --- common/obj-backend-fs.c | 65 +++++++++++++++++++++++------------------ 1 file changed, 36 insertions(+), 29 deletions(-) diff --git a/common/obj-backend-fs.c b/common/obj-backend-fs.c index 94a4dbd2..fde1d0ad 100644 --- a/common/obj-backend-fs.c +++ b/common/obj-backend-fs.c @@ -5,6 +5,7 @@ #include "common.h" #include "utils.h" #include "obj-backend.h" +#include "cf-v2-storage.h" #ifndef WIN32 #include @@ -25,28 +26,24 @@ typedef struct FsPriv { int dir_len; } FsPriv; -static void +static int id_to_path (FsPriv *priv, const char *obj_id, char path[], const char *repo_id, int version) { - char *pos = path; + char *dir; int n; - #if defined MIGRATION || defined SEAFILE_CLIENT - if (version > 0) { - n = snprintf (path, SEAF_PATH_MAX, "%s/%s/", priv->obj_dir, repo_id); - pos += n; - } -#else - n = snprintf (path, SEAF_PATH_MAX, "%s/%s/", priv->obj_dir, repo_id); - pos += n; + /* Preserve the native version-0 layout for offline export/migration. */ + if (version <= 0) + dir = g_strdup (priv->obj_dir); + else #endif - - memcpy (pos, obj_id, 2); - pos[2] = '/'; - pos += 3; - - memcpy (pos, obj_id + 2, 41 - 2); + dir = cf_v2_store_dir (priv->obj_dir, repo_id); + if (!dir) + return -1; + n = g_snprintf (path, SEAF_PATH_MAX, "%s/%.2s/%s", dir, obj_id, obj_id + 2); + g_free (dir); + return n >= SEAF_PATH_MAX ? -1 : 0; } static int @@ -61,7 +58,8 @@ obj_backend_fs_read (ObjBackend *bend, gsize tmp_len; GError *error = NULL; - id_to_path (bend->priv, obj_id, path, repo_id, version); + if (id_to_path (bend->priv, obj_id, path, repo_id, version) < 0) + return -1; /* seaf_debug ("object path: %s\n", path); */ @@ -69,7 +67,8 @@ obj_backend_fs_read (ObjBackend *bend, if (error) { #ifdef MIGRATION g_clear_error (&error); - id_to_path (bend->priv, obj_id, path, repo_id, 1); + if (id_to_path (bend->priv, obj_id, path, repo_id, 1) < 0) + return -1; g_file_get_contents (path, (gchar**)data, &tmp_len, &error); if (error) { seaf_debug ("[obj backend] Failed to read object %s: %s.\n", @@ -293,7 +292,8 @@ obj_backend_fs_write (ObjBackend *bend, { char path[SEAF_PATH_MAX]; - id_to_path (bend->priv, obj_id, path, repo_id, version); + if (id_to_path (bend->priv, obj_id, path, repo_id, version) < 0) + return -1; /* GTimeVal s, e; */ @@ -328,7 +328,8 @@ obj_backend_fs_exists (ObjBackend *bend, char path[SEAF_PATH_MAX]; SeafStat st; - id_to_path (bend->priv, obj_id, path, repo_id, version); + if (id_to_path (bend->priv, obj_id, path, repo_id, version) < 0) + return -1; if (seaf_stat (path, &st) == 0) return 1; @@ -344,7 +345,8 @@ obj_backend_fs_delete (ObjBackend *bend, { char path[SEAF_PATH_MAX]; - id_to_path (bend->priv, obj_id, path, repo_id, version); + if (id_to_path (bend->priv, obj_id, path, repo_id, version) < 0) + return -1; if (g_unlink (path) < 0 && errno != ENOENT) { seaf_warning ("Failed to delete object %s:%s: %s.\n", repo_id, obj_id, strerror (errno)); @@ -370,11 +372,15 @@ obj_backend_fs_foreach_obj (ObjBackend *bend, int ret = 0; #if defined MIGRATION || defined SEAFILE_CLIENT - if (version > 0) - obj_dir = g_build_filename (priv->obj_dir, repo_id, NULL); -#else - obj_dir = g_build_filename (priv->obj_dir, repo_id, NULL); + if (version <= 0) + obj_dir = g_strdup (priv->obj_dir); + else #endif + obj_dir = cf_v2_store_dir (priv->obj_dir, repo_id); + if (!obj_dir || strlen (obj_dir) + 130 >= SEAF_PATH_MAX) { + g_free (obj_dir); + return -1; + } dir_len = strlen (obj_dir); dir1 = g_dir_open (obj_dir, 0, NULL); @@ -432,8 +438,9 @@ obj_backend_fs_copy (ObjBackend *bend, char src_path[SEAF_PATH_MAX]; char dst_path[SEAF_PATH_MAX]; - id_to_path (bend->priv, obj_id, src_path, src_repo_id, src_version); - id_to_path (bend->priv, obj_id, dst_path, dst_repo_id, dst_version); + if (id_to_path (bend->priv, obj_id, src_path, src_repo_id, src_version) < 0 || + id_to_path (bend->priv, obj_id, dst_path, dst_repo_id, dst_version) < 0) + return -1; if (g_file_test (dst_path, G_FILE_TEST_EXISTS)) return 0; @@ -452,7 +459,7 @@ obj_backend_fs_copy (ObjBackend *bend, } return 0; #else - int ret = link (src_path, dst_path); + int ret = cf_v2_storage_copy (src_path, dst_path); if (ret < 0 && errno != EEXIST) { seaf_warning ("Failed to link %s to %s: %s.\n", src_path, dst_path, strerror(errno)); @@ -477,7 +484,7 @@ obj_backend_fs_remove_store (ObjBackend *bend, GError *error = NULL; int ret = 0; - obj_dir = g_build_filename (priv->obj_dir, store_id, NULL); + obj_dir = cf_v2_store_dir (priv->obj_dir, store_id); if (!obj_dir) return -1; From 9b0d6df0312c50b88fc13fe0d2b063e4affb5dfb Mon Sep 17 00:00:00 2001 From: servicememeai Date: Tue, 20 Oct 2026 15:04:38 +0900 Subject: [PATCH 02/13] fix(storage): connect Go ObjectStore to existing V2 filesystem resolver [skip ci] --- fileserver/objstore/objstore.go | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/fileserver/objstore/objstore.go b/fileserver/objstore/objstore.go index 91dcacdb..c4eb6850 100644 --- a/fileserver/objstore/objstore.go +++ b/fileserver/objstore/objstore.go @@ -44,6 +44,16 @@ func New(seafileConfPath string, seafileDataDir string, objType string) *ObjectS return obj } +// Route ordinary filesystem objects through the existing V2 resolver. A +// single committed RepoStorageId remains authoritative for C and Go readers. +func newRoutedFSBackend(confPath, seafileDataDir, objType string) (storageBackend, error) { + native, err := newFSBackend(seafileDataDir, objType) + if err != nil { + return nil, err + } + return newV2StorageBackend(confPath, native) +} + func newBackend(seafileConfPath, seafileDataDir, objType string) (storageBackend, error) { configPath := seafileConfPath info, statErr := os.Stat(configPath) @@ -54,7 +64,7 @@ func newBackend(seafileConfPath, seafileDataDir, objType string) (storageBackend config, err := ini.Load(configPath) if err != nil { if os.IsNotExist(err) { - return newFSBackend(seafileDataDir, objType) + return newRoutedFSBackend(configPath, seafileDataDir, objType) } return nil, err } @@ -69,7 +79,12 @@ func newBackend(seafileConfPath, seafileDataDir, objType string) (storageBackend } section, err := config.GetSection(sectionName) if err != nil || section.Key("name").String() == "" || section.Key("name").String() == "filesystem" { - return newFSBackend(seafileDataDir, objType) + return newRoutedFSBackend(configPath, seafileDataDir, objType) + } + // Legacy multi/S3 storage cannot coexist with the V2 filesystem root: + // a mixed C/Go backend would otherwise publish objects in different places. + if config.Section("cloudfile_v2").HasKey("auto_local_root") { + return nil, fmt.Errorf("V2 auto_local_root requires filesystem object backend") } if section.Key("name").String() != "s3" { if section.Key("name").String() == "multiple" { From beb3e5a5b954f7c99f7999b6226dbb3b2c417351 Mon Sep 17 00:00:00 2001 From: servicememeai Date: Tue, 20 Oct 2026 15:05:07 +0900 Subject: [PATCH 03/13] fix(storage): accept the real fileserver config path in V2 routing [skip ci] --- fileserver/objstore/cf_v2_storage.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/fileserver/objstore/cf_v2_storage.go b/fileserver/objstore/cf_v2_storage.go index 1d9deaa3..bd085d5b 100644 --- a/fileserver/objstore/cf_v2_storage.go +++ b/fileserver/objstore/cf_v2_storage.go @@ -72,8 +72,13 @@ func v2Lookup(repoID string) (string, string, string, error) { return route.String, key.String, state.String, err } -func newV2StorageBackend(confDir string, native *fsBackend) (storageBackend, error) { - name := filepath.Join(confDir, "seafile.conf") +func newV2StorageBackend(confPath string, native *fsBackend) (storageBackend, error) { + // Fileserver callers may pass the config directory or seafile.conf itself. + // Resolve both exactly as the existing newBackend() does. + name := confPath + if info, err := os.Stat(name); err == nil && info.IsDir() { + name = filepath.Join(name, "seafile.conf") + } cfg, err := ini.Load(name) if os.IsNotExist(err) { return native, nil From d23e71a0f009e9617b5610bf9c969101f69f3a2b Mon Sep 17 00:00:00 2001 From: servicememeai Date: Tue, 20 Oct 2026 15:06:21 +0900 Subject: [PATCH 04/13] fix(storage): use one V2 reservation and activation lifecycle for new libraries [skip ci] --- server/repo-mgr.c | 40 +++++++++++++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/server/repo-mgr.c b/server/repo-mgr.c index de42a9fb..86504f4f 100644 --- a/server/repo-mgr.c +++ b/server/repo-mgr.c @@ -26,6 +26,7 @@ #include "seaf-db.h" #include "seaf-utils.h" #include "cf-storage.h" +#include "cf-v2-storage.h" #define REAP_TOKEN_INTERVAL 300 /* 5 mins */ #define DECRYPTED_TOKEN_TTL 3600 /* 1 hour */ @@ -2893,6 +2894,12 @@ seaf_repo_manager_del_repo_from_trash (SeafRepoManager *mgr, const char *repo_id, GError **error) { + /* Preserve the storage key for cold GC and never reuse deleted keys. */ + if (cf_v2_storage_retire (repo_id) < 0) { + g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL, + "Failed to retire library storage."); + return -1; + } /* As long as the repo is successfully moved into GarbageRepo table, * we consider this operation successful. */ @@ -3898,9 +3905,16 @@ create_repo_common (SeafRepoManager *mgr, } } - /* Default local allocation belongs before the initial commit, including - * native/CLI callers that bypass the CloudFile HTTP creation endpoint. */ - if (cf_pin_default_local_storage (repo_id) < 0) { + /* Allocate the default V2 key before the first commit, for native and + * API callers alike. Keep the historical class allocator only for + * deployments without a V2 root; never mix both allocation schemas. */ + if (cf_v2_storage_enabled ()) { + if (cf_v2_storage_reserve (repo_id) < 0) { + g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL, + "Failed to reserve library storage."); + return -1; + } + } else if (cf_pin_default_local_storage (repo_id) < 0) { g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL, "Failed to allocate default library storage."); return -1; @@ -3969,6 +3983,12 @@ create_repo_common (SeafRepoManager *mgr, seaf_repo_manager_update_repo_info (mgr, repo->id, repo->head->commit_id); + if (cf_v2_storage_activate (repo_id) < 0) { + g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL, + "Failed to activate library storage."); + goto out; + } + ret = 0; out: if (repo) @@ -4000,10 +4020,16 @@ seaf_repo_manager_create_new_repo (SeafRepoManager *mgr, repo_id = gen_uuid (); - /* Pin the storage class before create_repo_common writes the initial - * commit: RepoStorageId must already point at the target backend or the - * root commit lands in the default store and the repo becomes unreadable - * (the multi-storage router looks the commit up in the pinned store). */ + /* The V2 default root is automatic, with no per-library class selector. + * Do not persist an old cf_library_storage_key pin alongside the V2 + * registry or overwrite the reserved RepoStorageId on creation. */ + if (cf_v2_storage_enabled () && storage_id && storage_id[0]) { + g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, + "Explicit storage classes are unavailable with V2 local storage."); + g_free (repo_id); + return NULL; + } + /* Preserve the pre-existing storage-class API when no V2 root is used. */ if (storage_id && storage_id[0]) { if (cf_set_repo_storage_id (repo_id, storage_id) < 0) { seaf_warning ("Failed to pin repo %s to storage %s.\n", From c5d377ab77c36ee670e210f8a9567cd6ad675d3e Mon Sep 17 00:00:00 2001 From: servicememeai Date: Tue, 20 Oct 2026 15:06:42 +0900 Subject: [PATCH 05/13] fix(storage): fail closed when the configured V2 route database is absent [skip ci] --- common/cf-v2-storage.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/common/cf-v2-storage.c b/common/cf-v2-storage.c index e1416c8d..ee8272fb 100644 --- a/common/cf-v2-storage.c +++ b/common/cf-v2-storage.c @@ -76,9 +76,12 @@ cf_v2_store_dir (const char *native_obj_dir, const char *repo_id) char *root = NULL, *key_dir = NULL, *obj_type = NULL, *result = NULL; /* Preserve native offline export. Automatic-root discovery for export is * a separate follow-up; never add a SQL prerequisite to native recovery. */ - if (!cf_v2_storage_enabled () || !seaf->db) + if (!cf_v2_storage_enabled ()) return g_build_filename (native_obj_dir, repo_id, NULL); - if (!repo_id || !is_uuid_valid (repo_id) || !(root = storage_root ())) + /* A configured managed root must never revert to native storage when + * database initialization fails: reads and writes would diverge. */ + if (!seaf->db || !repo_id || !is_uuid_valid (repo_id) || + !(root = storage_root ())) return NULL; /* One query distinguishes unmapped native libraries from corrupt routes. * No cache: separate C/Go processes must see the same committed binding. */ From b347e9d0a6b91a9842361036caf68978ba99e9a2 Mon Sep 17 00:00:00 2001 From: servicememeai Date: Tue, 20 Oct 2026 15:08:47 +0900 Subject: [PATCH 06/13] test(storage): verify real Go constructor routes V2 objects and fails closed [skip ci] --- .../objstore/cf_v2_route_wiring_test.go | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 fileserver/objstore/cf_v2_route_wiring_test.go diff --git a/fileserver/objstore/cf_v2_route_wiring_test.go b/fileserver/objstore/cf_v2_route_wiring_test.go new file mode 100644 index 00000000..e0dc8f7c --- /dev/null +++ b/fileserver/objstore/cf_v2_route_wiring_test.go @@ -0,0 +1,72 @@ +package objstore + +import ( + "bytes" + "os" + "path/filepath" + "strings" + "testing" +) + +// Exercise the *production* object-store constructor, not a hand-made wrapper. +func TestV2ObjectStoreSelectsManagedRoute(t *testing.T) { + root, confDir := t.TempDir(), t.TempDir() + config := filepath.Join(confDir, "seafile.conf") + if err := os.WriteFile(config, []byte("[cloudfile_v2]\nauto_local_root="+root+"\n"), 0600); err != nil { + t.Fatal(err) + } + if err := os.Mkdir(filepath.Join(root, repoID), 0700); err != nil { + t.Fatal(err) + } + for _, kind := range []string{"commits", "fs", "blocks"} { + t.Run(kind, func(t *testing.T) { + for _, input := range []string{confDir, config} { + nativeDir := t.TempDir() + backend, err := newBackend(input, nativeDir, kind) + if err != nil { + t.Fatal(err) + } + routed, ok := backend.(*v2StorageBackend) + if !ok { + t.Fatalf("missing V2 route wrapper for %s", input) + } + routed.lookup = func(string) (string, string, string, error) { + return "auto-local:" + repoID, repoID, "active", nil + } + payload := []byte("C/Go storage route contract") + if err := backend.write(repoID, objID, bytes.NewReader(payload), true); err != nil { + t.Fatal(err) + } + var actual bytes.Buffer + if err := backend.read(repoID, objID, &actual); err != nil || !bytes.Equal(actual.Bytes(), payload) { + t.Fatalf("managed route: %v", err) + } + if _, err := os.Stat(filepath.Join(nativeDir, "storage", kind, repoID)); !os.IsNotExist(err) { + t.Fatal("V2 object leaked to native path") + } + // A broken registry must never be treated as an old native library. + routed.lookup = func(string) (string, string, string, error) { + return "auto-local:invalid", repoID, "active", nil + } + if err := backend.write(repoID, objID, strings.NewReader("incorrect"), false); err == nil { + t.Fatal("invalid binding was not rejected") + } + } + }) + } +} + +func TestV2RootRejectsMixedLegacyStorageBackends(t *testing.T) { + root := t.TempDir() + for _, backendName := range []string{"s3", "multiple"} { + confDir := t.TempDir() + value := "[cloudfile_v2]\nauto_local_root=" + root + + "\n[block_backend]\nname=" + backendName + "\n" + if err := os.WriteFile(filepath.Join(confDir, "seafile.conf"), []byte(value), 0600); err != nil { + t.Fatal(err) + } + if _, err := newBackend(confDir, t.TempDir(), "blocks"); err == nil { + t.Fatalf("mixed %s backend must fail rather than silently diverge", backendName) + } + } +} From 634bb21fd0b451fa3aa104512b7f294a90193414 Mon Sep 17 00:00:00 2001 From: servicememeai Date: Tue, 20 Oct 2026 15:09:07 +0900 Subject: [PATCH 07/13] test(storage): simulate managed C shard layout without database or mounts [skip ci] --- tests/local_storage/stubs.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/tests/local_storage/stubs.c b/tests/local_storage/stubs.c index 7e6648a8..61319045 100644 --- a/tests/local_storage/stubs.c +++ b/tests/local_storage/stubs.c @@ -11,8 +11,17 @@ gboolean is_object_id_valid(const char *id) { for(int i=0;i<40;i++) if (!(g_ascii_isdigit(id[i]) || (id[i]>='a' && id[i]<='f'))) return FALSE; return TRUE; } -char *cf_v2_store_dir(const char *root,const char *repo) { return g_build_filename(root,repo,NULL); } -gboolean cf_v2_storage_enabled(void) { return FALSE; } +char *cf_v2_store_dir(const char *root,const char *repo) { + const char *mounted=g_getenv("CF_TEST_V2_STORAGE_ROOT"); + if (!mounted) return g_build_filename(root,repo,NULL); + /* Model the existing V2 path contract in a disposable fixture: the final + * object-type directory is resolved by the same native helper in production. */ + char *type=g_path_get_basename(root); + char *r=g_build_filename(mounted,repo,"storage",type,repo,NULL); + g_free(type); + return r; +} +gboolean cf_v2_storage_enabled(void) { return g_getenv("CF_TEST_V2_STORAGE_ROOT") != NULL; } int cf_v2_storage_copy(const char *src,const char *dst) { return link(src,dst); } int seaf_stat(const char *path, SeafStat *st) { return stat(path,st); } int seaf_fstat(int fd, SeafStat *st) { return fstat(fd,st); } From ed6298a900d6861c6f4fbeda4040240bc98ce0c4 Mon Sep 17 00:00:00 2001 From: servicememeai Date: Tue, 20 Oct 2026 15:09:24 +0900 Subject: [PATCH 08/13] test(storage): exercise native and managed C paths without full build [skip ci] --- tests/local_storage/run.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/local_storage/run.sh b/tests/local_storage/run.sh index 4ef0ed0b..e988706e 100644 --- a/tests/local_storage/run.sh +++ b/tests/local_storage/run.sh @@ -14,5 +14,9 @@ for name in block object; do "${glib_libs[@]}" -o "$fixture/$name" mkdir "$fixture/$name-data" "$fixture/$name" "$fixture/$name-data" + # Run the identical native code through the V2 managed-library route, too. + mkdir "$fixture/$name-managed-data" "$fixture/$name-managed-root" + CF_TEST_V2_STORAGE_ROOT="$fixture/$name-managed-root" \ + "$fixture/$name" "$fixture/$name-managed-data" done -printf 'Native atomic staging, abort cleanup and enumeration passed\n' +printf 'Native and managed C object staging, cleanup and enumeration passed\n' From 4620c38fb24d370958b37a7fdc36bcee350feea8 Mon Sep 17 00:00:00 2001 From: servicememeai Date: Tue, 20 Oct 2026 15:10:09 +0900 Subject: [PATCH 09/13] test(storage): assert managed C objects never leak into native paths [skip ci] --- tests/local_storage/block.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/local_storage/block.c b/tests/local_storage/block.c index 540ddee1..4bf8aa4c 100644 --- a/tests/local_storage/block.c +++ b/tests/local_storage/block.c @@ -20,6 +20,10 @@ int main(int argc,char **argv) { h=b->open_block(b,repo,1,oid,BLOCK_WRITE);g_assert_nonnull(h);char *aborted=g_strdup(h->tmp_file); b->close_block(b,h);b->block_handle_free(b,h);g_assert_false(g_file_test(aborted,G_FILE_TEST_EXISTS)); char *data=NULL;gsize len;g_assert_true(g_file_get_contents(final,&data,&len,NULL));g_assert_cmpmem(data,len,"complete",8); + if (g_getenv("CF_TEST_V2_STORAGE_ROOT")) { + char *native=g_build_filename(argv[1],"storage","blocks",repo,"ab",oid+2,NULL); + g_assert_false(g_file_test(native,G_FILE_TEST_EXISTS));g_free(native); + } /* Unknown shard names and both old/new staging formats stay untouched. */ char *root=g_path_get_dirname(parent),*unknown_dir=g_build_filename(root,"zz",NULL); char *unknown=g_build_filename(unknown_dir,"do-not-delete",NULL); From e017a01afff6ecffb49d0327903d3c01786ff5c4 Mon Sep 17 00:00:00 2001 From: servicememeai Date: Tue, 20 Oct 2026 15:10:13 +0900 Subject: [PATCH 10/13] test(storage): assert managed C objects never leak into native paths [skip ci] --- tests/local_storage/object.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/local_storage/object.c b/tests/local_storage/object.c index ee029a58..508e7ab6 100644 --- a/tests/local_storage/object.c +++ b/tests/local_storage/object.c @@ -15,6 +15,10 @@ int main(int argc,char **argv) { g_assert_true(g_file_set_contents(orphan,"partial",-1,NULL));g_assert_true(g_file_set_contents(old,"partial",-1,NULL)); n=0;g_assert_cmpint(b->foreach_obj(b,repo,1,count,&n),==,0);g_assert_cmpint(n,==,1); char *data=NULL;gsize len;g_assert_true(g_file_get_contents(final,&data,&len,NULL));g_assert_cmpmem(data,len,"complete",8); + if (g_getenv("CF_TEST_V2_STORAGE_ROOT")) { + char *native=g_build_filename(argv[1],"storage","fs",repo,"ab",oid+2,NULL); + g_assert_false(g_file_test(native,G_FILE_TEST_EXISTS));g_free(native); + } /* Unknown shard names and both old/new staging formats stay untouched. */ char *root=g_path_get_dirname(parent),*unknown_dir=g_build_filename(root,"zz",NULL); char *unknown=g_build_filename(unknown_dir,"do-not-delete",NULL); From 3a3e24204702d931c6921542a0f8beb7ef5fec5b Mon Sep 17 00:00:00 2001 From: servicememeai Date: Tue, 20 Oct 2026 15:11:32 +0900 Subject: [PATCH 11/13] style(storage): format targeted Go routing contract test [skip ci] --- .../objstore/cf_v2_route_wiring_test.go | 122 +++++++++--------- 1 file changed, 61 insertions(+), 61 deletions(-) diff --git a/fileserver/objstore/cf_v2_route_wiring_test.go b/fileserver/objstore/cf_v2_route_wiring_test.go index e0dc8f7c..72f0a137 100644 --- a/fileserver/objstore/cf_v2_route_wiring_test.go +++ b/fileserver/objstore/cf_v2_route_wiring_test.go @@ -1,72 +1,72 @@ package objstore import ( - "bytes" - "os" - "path/filepath" - "strings" - "testing" + "bytes" + "os" + "path/filepath" + "strings" + "testing" ) // Exercise the *production* object-store constructor, not a hand-made wrapper. func TestV2ObjectStoreSelectsManagedRoute(t *testing.T) { - root, confDir := t.TempDir(), t.TempDir() - config := filepath.Join(confDir, "seafile.conf") - if err := os.WriteFile(config, []byte("[cloudfile_v2]\nauto_local_root="+root+"\n"), 0600); err != nil { - t.Fatal(err) - } - if err := os.Mkdir(filepath.Join(root, repoID), 0700); err != nil { - t.Fatal(err) - } - for _, kind := range []string{"commits", "fs", "blocks"} { - t.Run(kind, func(t *testing.T) { - for _, input := range []string{confDir, config} { - nativeDir := t.TempDir() - backend, err := newBackend(input, nativeDir, kind) - if err != nil { - t.Fatal(err) - } - routed, ok := backend.(*v2StorageBackend) - if !ok { - t.Fatalf("missing V2 route wrapper for %s", input) - } - routed.lookup = func(string) (string, string, string, error) { - return "auto-local:" + repoID, repoID, "active", nil - } - payload := []byte("C/Go storage route contract") - if err := backend.write(repoID, objID, bytes.NewReader(payload), true); err != nil { - t.Fatal(err) - } - var actual bytes.Buffer - if err := backend.read(repoID, objID, &actual); err != nil || !bytes.Equal(actual.Bytes(), payload) { - t.Fatalf("managed route: %v", err) - } - if _, err := os.Stat(filepath.Join(nativeDir, "storage", kind, repoID)); !os.IsNotExist(err) { - t.Fatal("V2 object leaked to native path") - } - // A broken registry must never be treated as an old native library. - routed.lookup = func(string) (string, string, string, error) { - return "auto-local:invalid", repoID, "active", nil - } - if err := backend.write(repoID, objID, strings.NewReader("incorrect"), false); err == nil { - t.Fatal("invalid binding was not rejected") - } - } - }) - } + root, confDir := t.TempDir(), t.TempDir() + config := filepath.Join(confDir, "seafile.conf") + if err := os.WriteFile(config, []byte("[cloudfile_v2]\nauto_local_root="+root+"\n"), 0600); err != nil { + t.Fatal(err) + } + if err := os.Mkdir(filepath.Join(root, repoID), 0700); err != nil { + t.Fatal(err) + } + for _, kind := range []string{"commits", "fs", "blocks"} { + t.Run(kind, func(t *testing.T) { + for _, input := range []string{confDir, config} { + nativeDir := t.TempDir() + backend, err := newBackend(input, nativeDir, kind) + if err != nil { + t.Fatal(err) + } + routed, ok := backend.(*v2StorageBackend) + if !ok { + t.Fatalf("missing V2 route wrapper for %s", input) + } + routed.lookup = func(string) (string, string, string, error) { + return "auto-local:" + repoID, repoID, "active", nil + } + payload := []byte("C/Go storage route contract") + if err := backend.write(repoID, objID, bytes.NewReader(payload), true); err != nil { + t.Fatal(err) + } + var actual bytes.Buffer + if err := backend.read(repoID, objID, &actual); err != nil || !bytes.Equal(actual.Bytes(), payload) { + t.Fatalf("managed route: %v", err) + } + if _, err := os.Stat(filepath.Join(nativeDir, "storage", kind, repoID)); !os.IsNotExist(err) { + t.Fatal("V2 object leaked to native path") + } + // A broken registry must never be treated as an old native library. + routed.lookup = func(string) (string, string, string, error) { + return "auto-local:invalid", repoID, "active", nil + } + if err := backend.write(repoID, objID, strings.NewReader("incorrect"), false); err == nil { + t.Fatal("invalid binding was not rejected") + } + } + }) + } } func TestV2RootRejectsMixedLegacyStorageBackends(t *testing.T) { - root := t.TempDir() - for _, backendName := range []string{"s3", "multiple"} { - confDir := t.TempDir() - value := "[cloudfile_v2]\nauto_local_root=" + root + - "\n[block_backend]\nname=" + backendName + "\n" - if err := os.WriteFile(filepath.Join(confDir, "seafile.conf"), []byte(value), 0600); err != nil { - t.Fatal(err) - } - if _, err := newBackend(confDir, t.TempDir(), "blocks"); err == nil { - t.Fatalf("mixed %s backend must fail rather than silently diverge", backendName) - } - } + root := t.TempDir() + for _, backendName := range []string{"s3", "multiple"} { + confDir := t.TempDir() + value := "[cloudfile_v2]\nauto_local_root=" + root + + "\n[block_backend]\nname=" + backendName + "\n" + if err := os.WriteFile(filepath.Join(confDir, "seafile.conf"), []byte(value), 0600); err != nil { + t.Fatal(err) + } + if _, err := newBackend(confDir, t.TempDir(), "blocks"); err == nil { + t.Fatalf("mixed %s backend must fail rather than silently diverge", backendName) + } + } } From cf112a04ee7a9b4ead803860063f13c2bd6a033d Mon Sep 17 00:00:00 2001 From: servicememeai Date: Tue, 20 Oct 2026 15:13:41 +0900 Subject: [PATCH 12/13] test(storage): require managed C FS path resolution in fixture --- tests/local_storage/object.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/local_storage/object.c b/tests/local_storage/object.c index 508e7ab6..fec6ae7b 100644 --- a/tests/local_storage/object.c +++ b/tests/local_storage/object.c @@ -7,7 +7,7 @@ static gboolean count(const char *r,int v,const char *id,void *p) {g_assert_cmps int main(int argc,char **argv) { g_assert_cmpint(argc,==,2);ObjBackend *b=obj_backend_fs_new(argv[1],"fs");g_assert_nonnull(b); g_assert_cmpint(b->write(b,repo,1,oid,"complete",8,TRUE),==,0); - char final[SEAF_PATH_MAX];id_to_path(b->priv,oid,final,repo,1); + char final[SEAF_PATH_MAX];g_assert_cmpint(id_to_path(b->priv,oid,final,repo,1),==,0); fail_write=1;g_assert_cmpint(b->write(b,repo,1,oid,"partial",7,TRUE),==,-1);fail_write=0; char *parent=g_path_get_dirname(final);GDir *dir=g_dir_open(parent,0,NULL);const char *name;int n=0; while ((name=g_dir_read_name(dir))) {g_assert_false(g_str_has_prefix(name,".cf-tmp-"));n++;}g_dir_close(dir);g_assert_cmpint(n,==,1); From 95b428cd490a46be133c03231b79230d345e3aa2 Mon Sep 17 00:00:00 2001 From: servicememeai Date: Tue, 20 Oct 2026 15:14:43 +0900 Subject: [PATCH 13/13] style(storage): align Go router constructor with gofmt --- fileserver/objstore/objstore.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/fileserver/objstore/objstore.go b/fileserver/objstore/objstore.go index c4eb6850..2943dfda 100644 --- a/fileserver/objstore/objstore.go +++ b/fileserver/objstore/objstore.go @@ -47,11 +47,11 @@ func New(seafileConfPath string, seafileDataDir string, objType string) *ObjectS // Route ordinary filesystem objects through the existing V2 resolver. A // single committed RepoStorageId remains authoritative for C and Go readers. func newRoutedFSBackend(confPath, seafileDataDir, objType string) (storageBackend, error) { - native, err := newFSBackend(seafileDataDir, objType) - if err != nil { - return nil, err - } - return newV2StorageBackend(confPath, native) + native, err := newFSBackend(seafileDataDir, objType) + if err != nil { + return nil, err + } + return newV2StorageBackend(confPath, native) } func newBackend(seafileConfPath, seafileDataDir, objType string) (storageBackend, error) {