diff --git a/cmd/windows_agent/main.go b/cmd/windows_agent/main.go index 391edae..6b7d54a 100644 --- a/cmd/windows_agent/main.go +++ b/cmd/windows_agent/main.go @@ -105,6 +105,7 @@ func main() { } prg := &agentService{} + prg.pregenStatus = make(map[string]bool) watchdog := newWatchdogService(prg) s, err := service.New(watchdog, svcConfig) diff --git a/cmd/windows_agent/service.go b/cmd/windows_agent/service.go index 2f0cd01..8279aea 100644 --- a/cmd/windows_agent/service.go +++ b/cmd/windows_agent/service.go @@ -21,7 +21,9 @@ import ( "github.com/kardianos/service" "github.com/sonroyaalmerol/pbs-plus/internal/agent" "github.com/sonroyaalmerol/pbs-plus/internal/agent/controllers" + "github.com/sonroyaalmerol/pbs-plus/internal/agent/nfs/vssfs" "github.com/sonroyaalmerol/pbs-plus/internal/agent/registry" + "github.com/sonroyaalmerol/pbs-plus/internal/store/constants" "github.com/sonroyaalmerol/pbs-plus/internal/syslog" "github.com/sonroyaalmerol/pbs-plus/internal/utils" "github.com/sonroyaalmerol/pbs-plus/internal/websockets" @@ -41,10 +43,12 @@ type AgentDrivesRequest struct { } type agentService struct { - svc service.Service - ctx context.Context - cancel context.CancelFunc - wg sync.WaitGroup + svc service.Service + ctx context.Context + cancel context.CancelFunc + wg sync.WaitGroup + pregenMu sync.Mutex + pregenStatus map[string]bool } func (p *agentService) Start(s service.Service) error { @@ -177,6 +181,31 @@ func (p *agentService) initializeDrives() error { if err != nil { return fmt.Errorf("failed to get local drives list: %w", err) } + for _, drive := range drives { + p.pregenMu.Lock() + pregenStatus, ok := p.pregenStatus[drive.Letter] + if pregenStatus && ok { + p.pregenMu.Unlock() + continue + } + p.pregenStatus[drive.Letter] = true + p.pregenMu.Unlock() + + driveLetter := drive.Letter + go func(letter string) { + defer func() { + p.pregenMu.Lock() + p.pregenStatus[letter] = false + p.pregenMu.Unlock() + }() + constants.RegenPauseChannels[letter] = make(chan struct{}) + if err := vssfs.PreGenerateHandles(p.ctx, fmt.Sprintf("%s:\\", letter), constants.RegenPauseChannels[letter]); err != nil { + if err != context.Canceled { + syslog.L.Errorf("Handle pre-generator (%s) error: %v", letter, err) + } + } + }(driveLetter) + } reqBody, err := json.Marshal(&AgentDrivesRequest{ Hostname: hostname, diff --git a/go.mod b/go.mod index e6d790b..cc71da6 100644 --- a/go.mod +++ b/go.mod @@ -6,24 +6,28 @@ require ( github.com/alexflint/go-filemutex v1.3.0 github.com/billgraziano/dpapi v0.5.0 github.com/coder/websocket v1.8.12 + github.com/cyphar/filepath-securejoin v0.3.6 + github.com/dgraph-io/badger/v4 v4.5.1 github.com/fsnotify/fsnotify v1.8.0 github.com/getlantern/systray v1.2.2 github.com/go-git/go-billy/v5 v5.6.2 github.com/gobwas/glob v0.2.3 github.com/golang-jwt/jwt v3.2.2+incompatible - github.com/hashicorp/golang-lru/v2 v2.0.7 github.com/kardianos/service v1.2.2 github.com/mxk/go-vss v1.2.0 github.com/stretchr/testify v1.10.0 github.com/willscott/go-nfs v0.0.3-0.20241211060635-96c060392d22 + github.com/zeebo/xxh3 v1.0.2 golang.org/x/crypto v0.32.0 golang.org/x/sys v0.29.0 golang.org/x/time v0.9.0 ) require ( - github.com/cyphar/filepath-securejoin v0.3.6 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/dgraph-io/ristretto/v2 v2.1.0 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect github.com/getlantern/context v0.0.0-20190109183933-c447772a6520 // indirect github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7 // indirect github.com/getlantern/golog v0.0.0-20190830074920-4ef2e798c2d7 // indirect @@ -32,12 +36,17 @@ require ( github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-stack/stack v1.8.0 // indirect + github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect + github.com/google/flatbuffers v24.12.23+incompatible // indirect + github.com/klauspost/compress v1.17.11 // indirect github.com/klauspost/cpuid/v2 v2.0.9 // indirect github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/rasky/go-xdr v0.0.0-20170124162913-1a41d1a06c93 // indirect github.com/willscott/go-nfs-client v0.0.0-20240104095149-b44639837b00 // indirect - github.com/zeebo/xxh3 v1.0.2 // indirect + go.opencensus.io v0.24.0 // indirect + golang.org/x/net v0.34.0 // indirect + google.golang.org/protobuf v1.36.3 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index e745850..183a646 100644 --- a/go.sum +++ b/go.sum @@ -1,15 +1,34 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/alexflint/go-filemutex v1.3.0 h1:LgE+nTUWnQCyRKbpoceKZsPQbs84LivvgwUymZXdOcM= github.com/alexflint/go-filemutex v1.3.0/go.mod h1:U0+VA/i30mGBlLCrFPGtTe9y6wGQfNAWPBTekHQ+c8A= github.com/billgraziano/dpapi v0.5.0 h1:pcxA17vyjbDqYuxCFZbgL9tYIk2xgbRZjRaIbATwh+8= github.com/billgraziano/dpapi v0.5.0/go.mod h1:lmEcZjRfLCSbUTsRu8V2ti6Q17MvnKn3N9gQqzDdTh0= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo= github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs= github.com/cyphar/filepath-securejoin v0.3.6 h1:4d9N5ykBnSp5Xn2JkhocYDkOpURL/18CYMpo6xB9uWM= github.com/cyphar/filepath-securejoin v0.3.6/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgraph-io/badger/v4 v4.5.1 h1:7DCIXrQjo1LKmM96YD+hLVJ2EEsyyoWxJfpdd56HLps= +github.com/dgraph-io/badger/v4 v4.5.1/go.mod h1:qn3Be0j3TfV4kPbVoK0arXCD1/nr1ftth6sbL5jxdoA= +github.com/dgraph-io/ristretto/v2 v2.1.0 h1:59LjpOJLNDULHh8MC4UaegN52lC4JnO2dITsie/Pa8I= +github.com/dgraph-io/ristretto/v2 v2.1.0/go.mod h1:uejeqfYXpUomfse0+lO+13ATz4TypQYLJZzBSAemuB4= +github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y= +github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/getlantern/context v0.0.0-20190109183933-c447772a6520 h1:NRUJuo3v3WGC/g5YiyF790gut6oQr5f3FBI88Wv0dx4= @@ -36,14 +55,38 @@ github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/flatbuffers v24.12.23+incompatible h1:ubBKR94NR4pXUCY/MUsRVzd9umNW7ht7EG9hHfS9FX8= +github.com/google/flatbuffers v24.12.23+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/kardianos/service v1.2.2 h1:ZvePhAHfvo0A7Mftk/tEzqEZ7Q4lgnR8sGz4xu1YX60= github.com/kardianos/service v1.2.2/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM= +github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= +github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -63,28 +106,58 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rasky/go-xdr v0.0.0-20170124162913-1a41d1a06c93 h1:UVArwN/wkKjMVhh2EQGC0tEc1+FqiLlvYXY5mQ2f8Wg= github.com/rasky/go-xdr v0.0.0-20170124162913-1a41d1a06c93/go.mod h1:Nfe4efndBz4TibWycNE+lqyJZiMX4ycx+QKV8Ta0f/o= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/willscott/go-nfs v0.0.3-0.20241211060635-96c060392d22 h1:XwNlLRyIm2Sbn1tPUgGqdLV3CAWJs+K+j0hFX3UXh8c= github.com/willscott/go-nfs v0.0.3-0.20241211060635-96c060392d22/go.mod h1:VhNccO67Oug787VNXcyx9JDI3ZoSpqoKMT/lWMhUIDg= github.com/willscott/go-nfs-client v0.0.0-20240104095149-b44639837b00 h1:U0DnHRZFzoIV1oFEZczg5XyPut9yxk9jjtax/9Bxr/o= github.com/willscott/go-nfs-client v0.0.0-20240104095149-b44639837b00/go.mod h1:Tq++Lr/FgiS3X48q5FETemXiSLGuYMQT2sPjYNPJSwA= +github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= +github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -94,14 +167,46 @@ golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU= +google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/Knetic/govaluate.v3 v3.0.0/go.mod h1:csKLBORsPbafmSCGTEh3U7Ozmsuq8ZSIlKk1bcqph0E= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/internal/agent/nfs/nfs.go b/internal/agent/nfs/nfs.go index e1ad66c..1f26822 100644 --- a/internal/agent/nfs/nfs.go +++ b/internal/agent/nfs/nfs.go @@ -14,6 +14,7 @@ import ( "github.com/sonroyaalmerol/pbs-plus/internal/agent/nfs/vssfs" "github.com/sonroyaalmerol/pbs-plus/internal/agent/registry" "github.com/sonroyaalmerol/pbs-plus/internal/agent/snapshots" + "github.com/sonroyaalmerol/pbs-plus/internal/store/constants" "github.com/sonroyaalmerol/pbs-plus/internal/syslog" "github.com/sonroyaalmerol/pbs-plus/internal/utils" nfs "github.com/willscott/go-nfs" @@ -93,11 +94,16 @@ func (s *NFSSession) Serve() error { syslog.L.Infof("[NFS.Serve] Serving NFS on port %s", port) - vssHandler, err := vssfs.NewVSSIDHandler(s.FS.(*vssfs.VSSFS), handler) + pauseCh, ok := constants.RegenPauseChannels[s.DriveLetter] + if !ok { + constants.RegenPauseChannels[s.DriveLetter] = make(chan struct{}) + pauseCh = constants.RegenPauseChannels[s.DriveLetter] + } + + vssHandler, err := vssfs.NewVSSIDHandler(s.FS.(*vssfs.VSSFS), handler, pauseCh) if err != nil { return fmt.Errorf("unable to handle nfs: %w", err) } - defer vssHandler.ClearHandles() return nfs.Serve(listener, vssHandler) } diff --git a/internal/agent/nfs/vssfs/handler.go b/internal/agent/nfs/vssfs/handler.go index d31a096..551d667 100644 --- a/internal/agent/nfs/vssfs/handler.go +++ b/internal/agent/nfs/vssfs/handler.go @@ -10,6 +10,7 @@ import ( "strings" "sync" + "github.com/dgraph-io/badger/v4" "github.com/go-git/go-billy/v5" nfs "github.com/willscott/go-nfs" ) @@ -18,35 +19,120 @@ const ( RootHandleID = uint64(0) // Reserved ID for root directory ) +var keyPool = sync.Pool{ + New: func() interface{} { + return make([]byte, 8) + }, +} + // VSSIDHandler uses VSSFS's stable file IDs for handle management type VSSIDHandler struct { nfs.Handler vssFS *VSSFS activeHandlesMu sync.RWMutex activeHandles map[uint64]string + db *badger.DB + pregenPauseCh chan struct{} + rootSlashPath string // Cached root path in slash format } -func NewVSSIDHandler(vssFS *VSSFS, underlyingHandler nfs.Handler) (*VSSIDHandler, error) { - return &VSSIDHandler{ +func NewVSSIDHandler(vssFS *VSSFS, underlyingHandler nfs.Handler, pregenPauseCh chan struct{}) (*VSSIDHandler, error) { + dbPath, err := getDBPath(vssFS.snapshot.DriveLetter) + if err != nil { + return nil, fmt.Errorf("failed to get handler db path: %w", err) + } + + if pregenPauseCh != nil { + pregenPauseCh <- struct{}{} + } + + // Configure Badger for better performance + opts := badger.DefaultOptions(dbPath) + opts.Logger = nil + opts.CompactL0OnClose = true // Compact L0 on close for faster opens + + db, err := badger.Open(opts) + if err != nil { + return nil, fmt.Errorf("failed to open handle database: %w", err) + } + + h := &VSSIDHandler{ Handler: underlyingHandler, vssFS: vssFS, activeHandles: make(map[uint64]string), - }, nil + db: db, + pregenPauseCh: pregenPauseCh, + rootSlashPath: filepath.ToSlash(vssFS.Root()) + "/", + } + + if err := h.recoverHandles(); err != nil { + db.Close() + return nil, fmt.Errorf("failed to recover handles: %w", err) + } + + if pregenPauseCh != nil { + <-pregenPauseCh + } + + return h, nil +} + +func (h *VSSIDHandler) recoverHandles() error { + return h.db.View(func(txn *badger.Txn) error { + opts := badger.DefaultIteratorOptions + opts.PrefetchValues = false // Keys-only iteration + it := txn.NewIterator(opts) + defer it.Close() + + h.activeHandlesMu.Lock() + defer h.activeHandlesMu.Unlock() + + for it.Rewind(); it.Valid(); it.Next() { + item := it.Item() + key := binary.BigEndian.Uint64(item.Key()) + err := item.Value(func(val []byte) error { + h.activeHandles[key] = string(val) + return nil + }) + if err != nil { + return err + } + } + return nil + }) +} + +func (h *VSSIDHandler) Close() error { + return h.db.Close() } func (h *VSSIDHandler) getHandle(key uint64) (string, bool) { h.activeHandlesMu.RLock() defer h.activeHandlesMu.RUnlock() - handle, ok := h.activeHandles[key] return handle, ok } -func (h *VSSIDHandler) storeHandle(key uint64, path string) { - h.activeHandlesMu.Lock() - defer h.activeHandlesMu.Unlock() +func (h *VSSIDHandler) storeHandle(key uint64, path string) error { + // Use pooled buffer for key + keyBytes := keyPool.Get().([]byte) + defer keyPool.Put(keyBytes) + binary.BigEndian.PutUint64(keyBytes, key) + + // Store persistently first + err := h.db.Update(func(txn *badger.Txn) error { + return txn.Set(keyBytes, []byte(path)) + }) + if err != nil { + return err + } + // Then update in-memory + h.activeHandlesMu.Lock() h.activeHandles[key] = path + h.activeHandlesMu.Unlock() + + return nil } func (h *VSSIDHandler) ToHandle(f billy.Filesystem, path []string) []byte { @@ -60,30 +146,35 @@ func (h *VSSIDHandler) ToHandle(f billy.Filesystem, path []string) []byte { return h.createHandle(RootHandleID, vssFS.Root()) } - // Convert NFS path to Windows format winPath := filepath.Join(path...) fullPath := filepath.Join(vssFS.Root(), winPath) - // Get or create stable ID info, err := vssFS.Stat(winPath) if err != nil { return nil } - fileID := info.(*VSSFileInfo).stableID return h.createHandle(fileID, fullPath) } func (h *VSSIDHandler) createHandle(fileID uint64, fullPath string) []byte { - // Add to cache if not exists + // Double-checked locking pattern if _, exists := h.getHandle(fileID); !exists { - h.storeHandle(fileID, fullPath) + h.activeHandlesMu.Lock() + if _, exists := h.activeHandles[fileID]; !exists { + if err := h.storeHandle(fileID, fullPath); err != nil { + h.activeHandlesMu.Unlock() + return nil + } + } + h.activeHandlesMu.Unlock() } - // Convert ID to 8-byte handle - handle := make([]byte, 8) - binary.BigEndian.PutUint64(handle, fileID) - return handle + // Use pooled buffer for handle + handleBytes := keyPool.Get().([]byte) + defer keyPool.Put(handleBytes) + binary.BigEndian.PutUint64(handleBytes, fileID) + return append([]byte{}, handleBytes...) } func (h *VSSIDHandler) FromHandle(handle []byte) (billy.Filesystem, []string, error) { @@ -96,18 +187,13 @@ func (h *VSSIDHandler) FromHandle(handle []byte) (billy.Filesystem, []string, er return h.vssFS, []string{}, nil } - // Retrieve cached path fullPath, exists := h.getHandle(fileID) if !exists { return nil, nil, &nfs.NFSStatusError{NFSStatus: nfs.NFSStatusStale} } - // Convert Windows path to NFS components - relativePath := strings.TrimPrefix( - filepath.ToSlash(fullPath), - filepath.ToSlash(h.vssFS.Root())+"/", - ) - + // Use cached root path for conversion + relativePath := strings.TrimPrefix(filepath.ToSlash(fullPath), h.rootSlashPath) var parts []string if relativePath != "" { parts = strings.Split(relativePath, "/") @@ -120,13 +206,35 @@ func (h *VSSIDHandler) HandleLimit() int { } func (h *VSSIDHandler) InvalidateHandle(fs billy.Filesystem, handle []byte) error { - // No-op for read-only filesystem + if len(handle) != 8 { + return fmt.Errorf("invalid handle length") + } + + fileID := binary.BigEndian.Uint64(handle) + keyBytes := keyPool.Get().([]byte) + defer keyPool.Put(keyBytes) + binary.BigEndian.PutUint64(keyBytes, fileID) + + // Delete from Badger first + err := h.db.Update(func(txn *badger.Txn) error { + return txn.Delete(keyBytes) + }) + if err != nil { + return err + } + + // Delete from in-memory + h.activeHandlesMu.Lock() + delete(h.activeHandles, fileID) + h.activeHandlesMu.Unlock() + return nil } func (h *VSSIDHandler) ClearHandles() { h.activeHandlesMu.Lock() - defer h.activeHandlesMu.Unlock() - h.activeHandles = make(map[uint64]string) + h.activeHandlesMu.Unlock() + + h.db.DropAll() } diff --git a/internal/agent/nfs/vssfs/helpers.go b/internal/agent/nfs/vssfs/helpers.go index cb98ae6..3f47ad4 100644 --- a/internal/agent/nfs/vssfs/helpers.go +++ b/internal/agent/nfs/vssfs/helpers.go @@ -3,6 +3,16 @@ package vssfs import ( + "context" + "encoding/binary" + "fmt" + "os" + "path/filepath" + "strings" + "unsafe" + + "github.com/dgraph-io/badger/v4" + "github.com/go-git/go-billy/v5/osfs" "golang.org/x/sys/windows" ) @@ -14,3 +24,156 @@ func skipPathWithAttributes(attrs uint32) bool { windows.FILE_ATTRIBUTE_RECALL_ON_OPEN| windows.FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS) != 0 } + +const ( + AppName = "pbs-plus" +) + +// GetDBPath returns the path to the database file in a hidden folder on C drive +func getDBPath(drivePath string) (string, error) { + // Create hidden folder path (C:\.pbs-plus) + appDir := filepath.Join("C:\\", "."+AppName) + handlerDir := filepath.Join(appDir, "handlers") + + if err := os.MkdirAll(handlerDir, 0700); err != nil { + return "", err + } + + // Set folder as hidden + if err := setHiddenAttribute(appDir); err != nil { + return "", err + } + + dbFileName := strings.ReplaceAll(drivePath, ":", "") + dbFileName = strings.ReplaceAll(dbFileName, "\\", "") + dbFileName = strings.ReplaceAll(dbFileName, "/", "") + dbFileName = strings.ToUpper(dbFileName) + ".db" + + return filepath.Join(handlerDir, dbFileName), nil +} + +// setHiddenAttribute sets the hidden attribute on Windows +func setHiddenAttribute(path string) error { + kernel32 := windows.NewLazyDLL("kernel32.dll") + setFileAttributes := kernel32.NewProc("SetFileAttributesW") + + ptr, err := windows.UTF16PtrFromString(path) + if err != nil { + return err + } + + // FILE_ATTRIBUTE_HIDDEN = 2 + r1, _, err := setFileAttributes.Call(uintptr(unsafe.Pointer(ptr)), 2) + if r1 == 0 { + return err + } + return nil +} + +func PreGenerateHandles(ctx context.Context, drivePath string, pauseCh chan struct{}) error { + dbPath, err := getDBPath(drivePath) + if err != nil { + return fmt.Errorf("failed to get db path: %w", err) + } + + vssFS := VSSFS{ + Filesystem: osfs.New(drivePath, osfs.WithBoundOS()), + snapshot: nil, + root: drivePath, + } + + opts := badger.DefaultOptions(dbPath) + opts.Logger = nil + db, err := badger.Open(opts) + if err != nil { + return fmt.Errorf("failed to open handle database for pre-generation: %w", err) + } + defer db.Close() + + root := vssFS.Root() // Precompute the root path once + + var processDir func(path string) error + processDir = func(path string) error { + select { + case <-ctx.Done(): + return ctx.Err() + case <-pauseCh: + pauseCh <- struct{}{} // Acknowledge pause + <-pauseCh // Wait for resume + default: + } + + files, err := vssFS.ReadDir(path) + if err != nil { + return err + } + + // Collect all file entries in the directory + type fileEntry struct { + fileID uint64 + fullPath string + isDir bool + } + var entries []fileEntry + + for _, file := range files { + fullPath := filepath.Join(path, file.Name()) + var fileID uint64 + var isDir bool + + if fi, ok := file.(*VSSFileInfo); ok { + fileID = fi.stableID + isDir = file.IsDir() + } else { + // Fallback to Stat if not VSSFileInfo + info, err := vssFS.Stat(fullPath) + if err != nil { + continue // Skip files that can't be stated + } + vssInfo := info.(*VSSFileInfo) + fileID = vssInfo.stableID + isDir = vssInfo.IsDir() + } + + entries = append(entries, fileEntry{ + fileID: fileID, + fullPath: fullPath, + isDir: isDir, + }) + } + + // Batch process all entries in a single transaction + keyBuf := make([]byte, 8) // Reusable buffer for key encoding + err = db.Update(func(txn *badger.Txn) error { + for _, entry := range entries { + binary.BigEndian.PutUint64(keyBuf, entry.fileID) + _, err := txn.Get(keyBuf) + if err == badger.ErrKeyNotFound { + value := filepath.Join(root, entry.fullPath) + if err := txn.Set(keyBuf, []byte(value)); err != nil { + return err + } + } else if err != nil { + return err + } + } + return nil + }) + if err != nil { + return fmt.Errorf("batch update failed: %w", err) + } + + // Recursively process directories + for _, entry := range entries { + if entry.isDir { + if err := processDir(entry.fullPath); err != nil { + return err + } + } + } + + return nil + } + + return processDir("") +} diff --git a/internal/agent/nfs/vssfs/vssfs.go b/internal/agent/nfs/vssfs/vssfs.go index 1a2962b..931dc8c 100644 --- a/internal/agent/nfs/vssfs/vssfs.go +++ b/internal/agent/nfs/vssfs/vssfs.go @@ -28,13 +28,11 @@ type VSSFS struct { var _ billy.Filesystem = (*VSSFS)(nil) func NewVSSFS(snapshot *snapshots.WinVSSSnapshot, baseDir string) billy.Filesystem { - fs := &VSSFS{ + return &VSSFS{ Filesystem: osfs.New(filepath.Join(snapshot.SnapshotPath, baseDir), osfs.WithBoundOS()), snapshot: snapshot, root: filepath.Join(snapshot.SnapshotPath, baseDir), } - - return fs } // Override write operations to return read-only errors diff --git a/internal/store/constants/channels.go b/internal/store/constants/channels.go new file mode 100644 index 0000000..94e67b5 --- /dev/null +++ b/internal/store/constants/channels.go @@ -0,0 +1,3 @@ +package constants + +var RegenPauseChannels = make(map[string]chan struct{})