diff options
| author | 2026-03-07 21:05:30 +0800 | |
|---|---|---|
| committer | 2026-03-07 21:16:17 +0800 | |
| commit | 446993c94dc34c0374e00f3f5f21ece72b18a9f6 (patch) | |
| tree | aeaba0879b711c58e122c81893df97e5a64b0233 | |
| parent | receivepack: Propagate the rename error properly (diff) | |
| signature | No signature | |
receivepack: Set permissions properly
| -rw-r--r-- | receivepack/internal/service/options.go | 15 | ||||
| -rw-r--r-- | receivepack/internal/service/quarantine.go | 41 | ||||
| -rw-r--r-- | receivepack/internal/service/quarantine_test.go | 64 | ||||
| -rw-r--r-- | receivepack/options.go | 11 | ||||
| -rw-r--r-- | receivepack/receivepack.go | 16 |
5 files changed, 138 insertions, 9 deletions
diff --git a/receivepack/internal/service/options.go b/receivepack/internal/service/options.go index 4ba06827..c3d6c961 100644 --- a/receivepack/internal/service/options.go +++ b/receivepack/internal/service/options.go @@ -1,6 +1,7 @@ package service import ( + "io/fs" "os" "codeberg.org/lindenii/furgit/objectid" @@ -8,11 +9,17 @@ import ( "codeberg.org/lindenii/furgit/refstore" ) +type PromotedObjectPermissions struct { + DirMode fs.FileMode + FileMode fs.FileMode +} + // Options configures one protocol-independent receive-pack service. type Options struct { - Algorithm objectid.Algorithm - Refs refstore.ReadWriteStore - ExistingObjects objectstore.Store - ObjectsRoot *os.Root + Algorithm objectid.Algorithm + Refs refstore.ReadWriteStore + ExistingObjects objectstore.Store + ObjectsRoot *os.Root + PromotedObjectPermissions *PromotedObjectPermissions // TODO: Hook and such callbacks. } diff --git a/receivepack/internal/service/quarantine.go b/receivepack/internal/service/quarantine.go index e028f54d..f263186b 100644 --- a/receivepack/internal/service/quarantine.go +++ b/receivepack/internal/service/quarantine.go @@ -71,6 +71,11 @@ func (service *Service) promoteQuarantineDir(quarantineName string, quarantineRo return err } + err = service.applyPromotedDirectoryPermissions(childRel) + if err != nil { + return err + } + err = service.promoteQuarantineDir(quarantineName, quarantineRoot, childRel) if err != nil { return err @@ -84,6 +89,7 @@ func (service *Service) promoteQuarantineDir(quarantineName string, quarantineRo path.Join(quarantineName, childRel), childRel, isLooseObjectShardPath(rel), + service.opts.PromotedObjectPermissions, ) if err == nil { continue @@ -126,7 +132,32 @@ func isHex(ch byte) bool { return ('0' <= ch && ch <= '9') || ('a' <= ch && ch <= 'f') || ('A' <= ch && ch <= 'F') } -func finalizeQuarantineFile(root *os.Root, src, dst string, skipCollisionCheck bool) error { +func (service *Service) applyPromotedDirectoryPermissions(name string) error { + if service.opts.PromotedObjectPermissions == nil { + return nil + } + + return service.opts.ObjectsRoot.Chmod(name, service.opts.PromotedObjectPermissions.DirMode) +} + +func applyPromotedFilePermissions( + root *os.Root, + name string, + perms *PromotedObjectPermissions, +) error { + if perms == nil { + return nil + } + + return root.Chmod(name, perms.FileMode) +} + +func finalizeQuarantineFile( + root *os.Root, + src, dst string, + skipCollisionCheck bool, + perms *PromotedObjectPermissions, +) error { const maxVanishedRetries = 5 for retries := 0; ; retries++ { @@ -135,7 +166,7 @@ func finalizeQuarantineFile(root *os.Root, src, dst string, skipCollisionCheck b case err == nil: _ = root.Remove(src) - return nil + return applyPromotedFilePermissions(root, dst, perms) case !errors.Is(err, fs.ErrExist): _, statErr := root.Stat(dst) if statErr == nil { @@ -143,7 +174,7 @@ func finalizeQuarantineFile(root *os.Root, src, dst string, skipCollisionCheck b } else if errors.Is(statErr, fs.ErrNotExist) { renameErr := root.Rename(src, dst) if renameErr == nil { - return nil + return applyPromotedFilePermissions(root, dst, perms) } err = renameErr @@ -163,7 +194,7 @@ func finalizeQuarantineFile(root *os.Root, src, dst string, skipCollisionCheck b if skipCollisionCheck { _ = root.Remove(src) - return nil + return applyPromotedFilePermissions(root, dst, perms) } equal, vanished, cmpErr := compareRootFiles(root, src, dst) @@ -185,7 +216,7 @@ func finalizeQuarantineFile(root *os.Root, src, dst string, skipCollisionCheck b _ = root.Remove(src) - return nil + return applyPromotedFilePermissions(root, dst, perms) } } diff --git a/receivepack/internal/service/quarantine_test.go b/receivepack/internal/service/quarantine_test.go index 795fb35d..b28868ed 100644 --- a/receivepack/internal/service/quarantine_test.go +++ b/receivepack/internal/service/quarantine_test.go @@ -9,6 +9,70 @@ import ( "codeberg.org/lindenii/furgit/objectstore/memory" ) +func TestPromoteQuarantineAppliesConfiguredPermissions(t *testing.T) { + t.Parallel() + + objectsDir := t.TempDir() + objectsRoot, err := os.OpenRoot(objectsDir) + if err != nil { + t.Fatalf("os.OpenRoot: %v", err) + } + + t.Cleanup(func() { + _ = objectsRoot.Close() + }) + + svc := New(Options{ + Algorithm: objectid.AlgorithmSHA1, + ExistingObjects: memory.New(objectid.AlgorithmSHA1), + ObjectsRoot: objectsRoot, + PromotedObjectPermissions: &PromotedObjectPermissions{ + DirMode: 0o751, + FileMode: 0o640, + }, + }) + + quarantineName, quarantineRoot, err := svc.createQuarantineRoot() + if err != nil { + t.Fatalf("createQuarantineRoot: %v", err) + } + + t.Cleanup(func() { + _ = quarantineRoot.Close() + _ = objectsRoot.RemoveAll(quarantineName) + }) + + if err := quarantineRoot.Mkdir("ab", 0o700); err != nil { + t.Fatalf("Mkdir(ab): %v", err) + } + + if err := quarantineRoot.WriteFile(path.Join("ab", "cdef"), []byte("payload"), 0o600); err != nil { + t.Fatalf("WriteFile(quarantine loose): %v", err) + } + + if err := svc.promoteQuarantine(quarantineName, quarantineRoot); err != nil { + t.Fatalf("promoteQuarantine: %v", err) + } + + dirInfo, err := objectsRoot.Stat("ab") + if err != nil { + t.Fatalf("Stat(ab): %v", err) + } + + if got := dirInfo.Mode().Perm(); got != 0o751 { + t.Fatalf("dir mode = %o, want 751", got) + } + + fileInfo, err := objectsRoot.Stat(path.Join("ab", "cdef")) + if err != nil { + t.Fatalf("Stat(ab/cdef): %v", err) + } + + if got := fileInfo.Mode().Perm(); got != 0o640 { + t.Fatalf("file mode = %o, want 640", got) + } +} + func TestPromoteQuarantineTreatsExistingLooseObjectAsSuccess(t *testing.T) { t.Parallel() diff --git a/receivepack/options.go b/receivepack/options.go index 7763854a..76b908fd 100644 --- a/receivepack/options.go +++ b/receivepack/options.go @@ -1,6 +1,7 @@ package receivepack import ( + "io/fs" "os" "codeberg.org/lindenii/furgit/objectid" @@ -8,6 +9,13 @@ import ( "codeberg.org/lindenii/furgit/refstore" ) +// PromotedObjectPermissions configures the destination permissions applied to +// objects and directories promoted out of quarantine. +type PromotedObjectPermissions struct { + DirMode fs.FileMode + FileMode fs.FileMode +} + // Options configures one receive-pack invocation. type Options struct { // GitProtocol is the raw Git protocol version string from the transport, @@ -23,6 +31,9 @@ type Options struct { // ObjectsRoot is the permanent object storage root beneath which per-push // quarantine directories are derived. ObjectsRoot *os.Root + // PromotedObjectPermissions, when non-nil, is applied to objects and + // directories moved from quarantine into the permanent object store. + PromotedObjectPermissions *PromotedObjectPermissions // TODO: Hook and policy callbacks. } diff --git a/receivepack/receivepack.go b/receivepack/receivepack.go index ec5d956b..5a70715d 100644 --- a/receivepack/receivepack.go +++ b/receivepack/receivepack.go @@ -71,6 +71,9 @@ func ReceivePack( Refs: opts.Refs, ExistingObjects: opts.ExistingObjects, ObjectsRoot: opts.ObjectsRoot, + PromotedObjectPermissions: translatePromotedObjectPermissions( + opts.PromotedObjectPermissions, + ), }) result, err := svc.Execute(ctx, serviceReq) @@ -90,3 +93,16 @@ func ReceivePack( return nil } + +func translatePromotedObjectPermissions( + perms *PromotedObjectPermissions, +) *service.PromotedObjectPermissions { + if perms == nil { + return nil + } + + return &service.PromotedObjectPermissions{ + DirMode: perms.DirMode, + FileMode: perms.FileMode, + } +} |
