fix(run): keep the permission bits of extracted artifact files

Signed-off-by: bircni <bircni@icloud.com>
This commit is contained in:
bircni committed 2026-10-12 20:36:16 +02:00
1 parent f51af883c6
commit a24acef26c
2 files changed
+29 -2

No files matched your search

+3 -2
View File
@@ -259,7 +259,8 @@ func extractZip(ctx context.Context, body io.Reader, dir string, clobber, skipEx
return nil return nil
} }
// extractFile writes one archive entry to path with the permissions the umask allows. // extractFile writes one archive entry to path with its permission bits, such as an executable's,
// as far as the umask allows; special bits such as setuid are dropped.
func extractFile(entry *zip.File, path string, clobber, skipExisting bool) error { func extractFile(entry *zip.File, path string, clobber, skipExisting bool) error {
if err := os.MkdirAll(filepath.Dir(path), 0o777); err != nil { if err := os.MkdirAll(filepath.Dir(path), 0o777); err != nil {
return err return err
@@ -268,7 +269,7 @@ func extractFile(entry *zip.File, path string, clobber, skipExisting bool) error
if clobber { if clobber {
flags = os.O_WRONLY | os.O_CREATE | os.O_TRUNC flags = os.O_WRONLY | os.O_CREATE | os.O_TRUNC
} }
file, err := os.OpenFile(path, flags, 0o666) file, err := os.OpenFile(path, flags, cmp.Or(entry.Mode().Perm(), 0o666))
if errors.Is(err, os.ErrExist) && skipExisting { if errors.Is(err, os.ErrExist) && skipExisting {
return nil return nil
} }
+26
View File
@@ -10,6 +10,7 @@ import (
"net/http" "net/http"
"os" "os"
"path/filepath" "path/filepath"
"runtime"
"testing" "testing"
"gitea.dev/tea/internal/clierr" "gitea.dev/tea/internal/clierr"
@@ -127,6 +128,31 @@ func TestRunDownloadExtractsArtifactsByName(t *testing.T) {
}) })
} }
func TestExtractZipKeepsPermissionBits(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("Windows has no permission bits")
}
var archive bytes.Buffer
writer := zip.NewWriter(&archive)
for name, mode := range map[string]os.FileMode{"bin/tool": 0o755, "setuid": 0o755 | os.ModeSetuid, "data": 0o644} {
header := &zip.FileHeader{Name: name}
header.SetMode(mode)
file, err := writer.CreateHeader(header)
require.NoError(t, err)
_, err = file.Write([]byte(name))
require.NoError(t, err)
}
require.NoError(t, writer.Close())
dir := t.TempDir()
require.NoError(t, extractZip(context.Background(), &archive, dir, false, false))
for name, executable := range map[string]bool{"bin/tool": true, "setuid": true, "data": false} {
info, err := os.Stat(filepath.Join(dir, name))
require.NoError(t, err)
require.Equal(t, executable, info.Mode().Perm()&0o100 != 0, name)
require.Zero(t, info.Mode()&os.ModeSetuid, name)
}
}
// filepathReplace puts dir in place of {dir} in text. // filepathReplace puts dir in place of {dir} in text.
func filepathReplace(text, dir string) string { func filepathReplace(text, dir string) string {
return string(bytes.ReplaceAll([]byte(text), []byte("{dir}"), []byte(dir))) return string(bytes.ReplaceAll([]byte(text), []byte("{dir}"), []byte(dir)))