mirror of
https://gitea.com/gitea/runner.git
synced 2026-11-06 23:30:29 +00:00
139 lines
4.1 KiB
Go
139 lines
4.1 KiB
Go
// Copyright 2026 The Gitea Authors. All rights reserved.
|
|
// Copyright 2020 The nektos/act Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package container
|
|
|
|
import (
|
|
"archive/tar"
|
|
"bufio"
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"io/fs"
|
|
"path"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"gitea.com/gitea/runner/act/common"
|
|
"gitea.com/gitea/runner/act/filecollector"
|
|
)
|
|
|
|
// TarWriter takes directories and files named relative to the destination, owned by the job user.
|
|
type TarWriter struct {
|
|
writer *tar.Writer
|
|
uid, gid int
|
|
prefix string
|
|
dirs map[string]bool
|
|
}
|
|
|
|
func (w *TarWriter) Write(data []byte) (int, error) {
|
|
return w.writer.Write(data)
|
|
}
|
|
|
|
func (w *TarWriter) WriteHeader(hdr *tar.Header) error {
|
|
name := strings.TrimSuffix(hdr.Name, "/")
|
|
if !fs.ValidPath(name) {
|
|
return fmt.Errorf("tar entry %q escapes the destination", hdr.Name)
|
|
}
|
|
if hdr.Typeflag != tar.TypeDir && hdr.Typeflag != tar.TypeReg {
|
|
return fmt.Errorf("tar entry %q: unsupported type %q", hdr.Name, hdr.Typeflag)
|
|
}
|
|
for index := range len(name) {
|
|
if dir := name[:index]; name[index] == '/' && !w.dirs[dir] {
|
|
if err := w.write(&tar.Header{Typeflag: tar.TypeDir, Name: path.Join(w.prefix, dir), Mode: 0o755}); err != nil {
|
|
return err
|
|
}
|
|
w.dirs[dir] = true
|
|
}
|
|
}
|
|
if hdr.Typeflag == tar.TypeDir {
|
|
w.dirs[name] = true
|
|
}
|
|
entry := *hdr
|
|
entry.Name = path.Join(w.prefix, name)
|
|
return w.write(&entry)
|
|
}
|
|
|
|
func (w *TarWriter) write(hdr *tar.Header) error {
|
|
hdr.Uid, hdr.Gid, hdr.Uname, hdr.Gname = w.uid, w.gid, "", "" // tar running as root prefers names over ids
|
|
if hdr.Typeflag == tar.TypeDir {
|
|
hdr.Name += "/"
|
|
}
|
|
return w.writer.WriteHeader(hdr)
|
|
}
|
|
|
|
func writeTarStream(dst io.Writer, uid, gid int, prefix string, parents []string, write func(*TarWriter) error) error {
|
|
tw := &TarWriter{writer: tar.NewWriter(dst), uid: uid, gid: gid, prefix: prefix, dirs: map[string]bool{}}
|
|
for _, name := range parents {
|
|
if err := tw.write(&tar.Header{Typeflag: tar.TypeDir, Name: name, Mode: 0o755}); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if err := write(tw); errors.Is(err, io.EOF) {
|
|
return fmt.Errorf("tar stream: %v", err) // %v, as a wrapped io.EOF reads as the archive's end
|
|
} else if err != nil {
|
|
return err
|
|
}
|
|
return tw.writer.Close()
|
|
}
|
|
|
|
func pipeTar(uid, gid int, prefix string, parents []string, write func(*TarWriter) error) *io.PipeReader {
|
|
reader, writer := io.Pipe()
|
|
go func() { writer.CloseWithError(writeTarStream(writer, uid, gid, prefix, parents, write)) }()
|
|
return reader
|
|
}
|
|
|
|
// startedArchive returns reader once its first byte arrives, so failing to start archiving errors here, not on read.
|
|
func startedArchive(reader io.ReadCloser) (io.ReadCloser, error) {
|
|
buffered := bufio.NewReader(reader)
|
|
if _, err := buffered.Peek(1); err != nil {
|
|
reader.Close()
|
|
return nil, err
|
|
}
|
|
return struct {
|
|
io.Reader
|
|
io.Closer
|
|
}{buffered, reader}, nil
|
|
}
|
|
|
|
func writeFilesTar(ctx context.Context, w io.Writer, uid, gid int, files ...*FileEntry) error {
|
|
tw := tar.NewWriter(w)
|
|
for _, file := range files {
|
|
common.Logger(ctx).Debugf("Writing entry to tarball %s len:%d", file.Name, len(file.Body))
|
|
if err := tw.WriteHeader(&tar.Header{
|
|
Name: file.Name,
|
|
Mode: file.Mode,
|
|
Size: int64(len(file.Body)),
|
|
Uid: uid,
|
|
Gid: gid,
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
if _, err := tw.Write([]byte(file.Body)); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return tw.Close()
|
|
}
|
|
|
|
// writeDirTar prefixes entries with dstPath for extraction at root.
|
|
func writeDirTar(ctx context.Context, w io.Writer, dstPath, srcPath string, useGitIgnore, skipGitDir bool, uid, gid int, executable bool) error {
|
|
logger := common.Logger(ctx)
|
|
tw := tar.NewWriter(w)
|
|
srcPrefix := strings.TrimSuffix(filepath.Dir(srcPath), string(filepath.Separator)) + string(filepath.Separator)
|
|
logger.Debugf("Stripping prefix:%s src:%s", srcPrefix, srcPath)
|
|
|
|
if err := filepath.Walk(srcPath, (&filecollector.FileCollector{
|
|
SrcPath: srcPath,
|
|
SrcPrefix: srcPrefix,
|
|
SkipGitDir: skipGitDir,
|
|
UseGitIgnore: useGitIgnore,
|
|
Handler: &filecollector.TarCollector{TarWriter: tw, UID: uid, GID: gid, DstDir: dstPath[1:], Executable: executable},
|
|
}).CollectFiles(ctx)); err != nil {
|
|
return err
|
|
}
|
|
return tw.Close()
|
|
}
|