From 433dea41e8c42616b03d80b17dd011354c284095 Mon Sep 17 00:00:00 2001
From: Pascal Bleser
Date: Wed, 29 Jul 2026 11:18:39 +0200
Subject: [PATCH] feat(posixfs): #3182 add basepath option in the "posixfs
scan" command
* add support for specifying a basepath using -p when running the
posixfs scan command, to indicate a directory under which to start
scanning, or a singular file to scan, as opposed to scanning from the
storage root directory as is the default behaviour
* implements https://github.com/opencloud-eu/opencloud/issues/3182
---
opencloud/pkg/command/posixfs.go | 47 +++++++++++++++++++++++++++++--
pkg/x/path/filepathx/path.go | 26 +++++++++++++++++
pkg/x/path/filepathx/path_test.go | 23 +++++++++++++++
3 files changed, 94 insertions(+), 2 deletions(-)
diff --git a/opencloud/pkg/command/posixfs.go b/opencloud/pkg/command/posixfs.go
index d43be52749..e38a400afb 100644
--- a/opencloud/pkg/command/posixfs.go
+++ b/opencloud/pkg/command/posixfs.go
@@ -13,6 +13,7 @@ import (
"github.com/opencloud-eu/opencloud/pkg/config/configlog"
"github.com/opencloud-eu/opencloud/pkg/config/parser"
oclog "github.com/opencloud-eu/opencloud/pkg/log"
+ "github.com/opencloud-eu/opencloud/pkg/x/path/filepathx"
storageUsersParser "github.com/opencloud-eu/opencloud/services/storage-users/pkg/config/parser"
"github.com/opencloud-eu/opencloud/services/storage-users/pkg/event"
"github.com/opencloud-eu/opencloud/services/storage-users/pkg/revaconfig"
@@ -95,6 +96,39 @@ func scanCmd(ocCfg *config.Config) *cobra.Command {
os.Exit(1)
}
+ storageRoot := cfg.Drivers.Posix.Root
+ root := storageRoot
+ defaultRoot := true
+ if v, err := cmd.Flags().GetString("basepath"); err != nil {
+ fmt.Fprintf(os.Stderr, "Failed to parse command-line parameter '--basepath': %v\n", err)
+ os.Exit(1)
+ } else if v != "" {
+ root = v
+ if !filepath.IsAbs(v) {
+ if v, err = filepath.Abs(v); err != nil {
+ fmt.Fprintf(os.Stderr, "Failed to make the basepath mentioned using '--basepath' absolute: %v\n", err)
+ os.Exit(1)
+ } else {
+ root = v
+ }
+ } else {
+ root = v
+ }
+ root = filepath.Clean(root)
+ defaultRoot = false
+ }
+
+ // ensure that, if a basepath has been indicated, it is under the storage root
+ if !defaultRoot {
+ if contained, err := filepathx.IsSameOrContainedBy(storageRoot, root); err != nil {
+ fmt.Fprintf(os.Stderr, "Failed to determine whether the specified basepath %q is contained by the storage root %q: %v\n", root, storageRoot, err)
+ os.Exit(1)
+ } else if !contained {
+ fmt.Fprintf(os.Stderr, "The specified basepath %q is neither the storage root %q, nor a subdirectory thereof, nor a file underneath it\n", root, storageRoot)
+ os.Exit(1)
+ }
+ }
+
// We want to initialize the driver but disable scanfs on boot, so we can trigger it manually afterwards
drivers := revaconfig.StorageProviderDrivers(cfg)
drivers["posix"] = revaconfig.Posix(cfg, false, false)
@@ -112,6 +146,10 @@ func scanCmd(ocCfg *config.Config) *cobra.Command {
oclog.Pretty(true),
oclog.Color(false)).Logger
+ if !defaultRoot {
+ log = log.With().Str("basepath", root).Logger()
+ }
+
f, ok := registry.NewFuncs["posix"]
if !ok {
fmt.Fprintf(os.Stderr, "posix driver not found in registry\n")
@@ -130,8 +168,12 @@ func scanCmd(ocCfg *config.Config) *cobra.Command {
os.Exit(1)
}
- fmt.Println("Starting posixfs scan...")
- err = cacher.WarmupIDCache(cfg.Drivers.Posix.Root, true, false)
+ if defaultRoot {
+ fmt.Println("Starting posixfs scan...")
+ } else {
+ fmt.Printf("Starting posixfs scan at '%s'...\n", root)
+ }
+ err = cacher.WarmupIDCache(root, true, false)
if err != nil {
fmt.Fprintf(os.Stderr, "Scan failed: %v\n", err)
return err
@@ -141,6 +183,7 @@ func scanCmd(ocCfg *config.Config) *cobra.Command {
return nil
},
}
+ cmd.Flags().StringP("basepath", "p", "", "the root under which to scan files, which may be a directory or a file (when omitted, detaults to using the storage root)")
return cmd
}
diff --git a/pkg/x/path/filepathx/path.go b/pkg/x/path/filepathx/path.go
index 8101e89331..d105290fc3 100644
--- a/pkg/x/path/filepathx/path.go
+++ b/pkg/x/path/filepathx/path.go
@@ -1,7 +1,9 @@
package filepathx
import (
+ "fmt"
"path/filepath"
+ "strings"
)
// JailJoin joins any number of path elements into a single path,
@@ -10,3 +12,27 @@ import (
func JailJoin(jail string, elem ...string) string {
return filepath.Join(jail, filepath.Join(append([]string{"/"}, elem...)...))
}
+
+// Determines whether the file or directory 'child' is same as or underneath the directory 'parent'.
+//
+// Note that 'parent' is expected to be a directory.
+func IsSameOrContainedBy(parent string, child string) (bool, error) {
+ absParent, err := filepath.Abs(parent)
+ if err != nil {
+ return false, fmt.Errorf("failed to make parent directory absolute: %q: %w", parent, err)
+ }
+
+ absChild, err := filepath.Abs(child)
+ if err != nil {
+ return false, fmt.Errorf("failed to make child file/directory absolute: %q: %w", child, err)
+ }
+
+ rel, err := filepath.Rel(absParent, absChild)
+ if err != nil {
+ return false, fmt.Errorf("failed to determine the relative path between the parent directory %q and the child file/directory: %q: %w", absParent, absChild, err)
+ }
+ if rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator)) {
+ return false, nil
+ }
+ return true, nil
+}
diff --git a/pkg/x/path/filepathx/path_test.go b/pkg/x/path/filepathx/path_test.go
index ca8782d942..c5b098f21d 100644
--- a/pkg/x/path/filepathx/path_test.go
+++ b/pkg/x/path/filepathx/path_test.go
@@ -1,9 +1,12 @@
package filepathx_test
import (
+ "fmt"
+ "strings"
"testing"
"github.com/opencloud-eu/opencloud/pkg/x/path/filepathx"
+ "github.com/stretchr/testify/require"
)
func TestJailJoin(t *testing.T) {
@@ -61,3 +64,23 @@ func TestJailJoin(t *testing.T) {
})
}
}
+
+func TestIsSameOrContainedBy(t *testing.T) {
+ for _, tt := range []struct {
+ parent string
+ child string
+ expected bool
+ }{
+ {"foo", "foo", true},
+ {"/foo", "/foo", true},
+ {"foo", "foo/bar", true},
+ {"foo", "bar", false},
+ } {
+ t.Run(fmt.Sprintf("%s: %s vs %s", t.Name(), strings.ReplaceAll(tt.parent, "/", "."), strings.ReplaceAll(tt.child, "/", ".")), func(t *testing.T) {
+ require := require.New(t)
+ b, err := filepathx.IsSameOrContainedBy(tt.parent, tt.child)
+ require.NoError(err)
+ require.Equal(tt.expected, b)
+ })
+ }
+}