-
-
Notifications
You must be signed in to change notification settings - Fork 276
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
148 additions
and
55 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,4 +10,4 @@ | |
!testdata/*.php | ||
!testdata/*.txt | ||
!build-static.sh | ||
!embed/* | ||
!app.tar |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,91 +1,189 @@ | ||
package frankenphp | ||
|
||
import ( | ||
"archive/tar" | ||
"bytes" | ||
"crypto/md5" | ||
"embed" | ||
_ "embed" | ||
"encoding/hex" | ||
"errors" | ||
"fmt" | ||
"io" | ||
"io/fs" | ||
"log" | ||
"os" | ||
"path" | ||
"path/filepath" | ||
"runtime" | ||
"strings" | ||
"time" | ||
) | ||
|
||
const embedDir = "embed" | ||
|
||
// The path of the embedded PHP application (empty if none) | ||
var EmbeddedAppPath string | ||
|
||
//go:embed all:embed | ||
var embeddedApp embed.FS | ||
//go:embed app.tar | ||
var embeddedApp []byte | ||
|
||
func init() { | ||
entries, err := embeddedApp.ReadDir(embedDir) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
if len(entries) == 1 && entries[0].Name() == ".gitignore" { | ||
//no embedded app | ||
if len(embeddedApp) == 0 { | ||
// No embedded app | ||
return | ||
} | ||
|
||
e, err := os.Executable() | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
e, err = filepath.EvalSymlinks(e) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
// TODO: use XXH3 instead of MD5 | ||
h := md5.Sum([]byte(e)) | ||
h := md5.Sum(embeddedApp) | ||
appPath := filepath.Join(os.TempDir(), "frankenphp_"+hex.EncodeToString(h[:])) | ||
|
||
if err := os.RemoveAll(appPath); err != nil { | ||
panic(err) | ||
} | ||
if err := copyToDisk(appPath, embedDir, entries); err != nil { | ||
if err := untar(appPath); err != nil { | ||
os.RemoveAll(appPath) | ||
panic(err) | ||
} | ||
|
||
EmbeddedAppPath = appPath | ||
} | ||
|
||
func copyToDisk(appPath string, currentDir string, entries []fs.DirEntry) error { | ||
if err := os.Mkdir(appPath+strings.TrimPrefix(currentDir, embedDir), 0700); err != nil { | ||
return err | ||
} | ||
|
||
for _, entry := range entries { | ||
name := entry.Name() | ||
|
||
if entry.IsDir() { | ||
entries, err := embeddedApp.ReadDir(currentDir + "/" + name) | ||
// untar reads the tar file from r and writes it into dir. | ||
// | ||
// Adapted from https://github.com/golang/build/blob/master/cmd/buildlet/buildlet.go | ||
func untar(dir string) (err error) { | ||
t0 := time.Now() | ||
nFiles := 0 | ||
madeDir := map[string]bool{} | ||
|
||
tr := tar.NewReader(bytes.NewReader(embeddedApp)) | ||
loggedChtimesError := false | ||
for { | ||
f, err := tr.Next() | ||
if err == io.EOF { | ||
break | ||
} | ||
if err != nil { | ||
return fmt.Errorf("tar error: %w", err) | ||
} | ||
if f.Typeflag == tar.TypeXGlobalHeader { | ||
// golang.org/issue/22748: git archive exports | ||
// a global header ('g') which after Go 1.9 | ||
// (for a bit?) contained an empty filename. | ||
// Ignore it. | ||
continue | ||
} | ||
rel, err := nativeRelPath(f.Name) | ||
if err != nil { | ||
return fmt.Errorf("tar file contained invalid name %q: %v", f.Name, err) | ||
} | ||
abs := filepath.Join(dir, rel) | ||
|
||
fi := f.FileInfo() | ||
mode := fi.Mode() | ||
switch { | ||
case mode.IsRegular(): | ||
// Make the directory. This is redundant because it should | ||
// already be made by a directory entry in the tar | ||
// beforehand. Thus, don't check for errors; the next | ||
// write will fail with the same error. | ||
dir := filepath.Dir(abs) | ||
if !madeDir[dir] { | ||
if err := os.MkdirAll(filepath.Dir(abs), mode.Perm()); err != nil { | ||
return err | ||
} | ||
madeDir[dir] = true | ||
} | ||
if runtime.GOOS == "darwin" && mode&0111 != 0 { | ||
// See comment in writeFile. | ||
err := os.Remove(abs) | ||
if err != nil && !errors.Is(err, fs.ErrNotExist) { | ||
return err | ||
} | ||
} | ||
wf, err := os.OpenFile(abs, os.O_RDWR|os.O_CREATE|os.O_TRUNC, mode.Perm()) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if err := copyToDisk(appPath, currentDir+"/"+name, entries); err != nil { | ||
n, err := io.Copy(wf, tr) | ||
if closeErr := wf.Close(); closeErr != nil && err == nil { | ||
err = closeErr | ||
} | ||
if err != nil { | ||
return fmt.Errorf("error writing to %s: %v", abs, err) | ||
} | ||
if n != f.Size { | ||
return fmt.Errorf("only wrote %d bytes to %s; expected %d", n, abs, f.Size) | ||
} | ||
modTime := f.ModTime | ||
if modTime.After(t0) { | ||
// Clamp modtimes at system time. See | ||
// golang.org/issue/19062 when clock on | ||
// buildlet was behind the gitmirror server | ||
// doing the git-archive. | ||
modTime = t0 | ||
} | ||
if !modTime.IsZero() { | ||
if err := os.Chtimes(abs, modTime, modTime); err != nil && !loggedChtimesError { | ||
// benign error. Gerrit doesn't even set the | ||
// modtime in these, and we don't end up relying | ||
// on it anywhere (the gomote push command relies | ||
// on digests only), so this is a little pointless | ||
// for now. | ||
log.Printf("error changing modtime: %v (further Chtimes errors suppressed)", err) | ||
loggedChtimesError = true // once is enough | ||
} | ||
} | ||
nFiles++ | ||
case mode.IsDir(): | ||
if err := os.MkdirAll(abs, mode.Perm()); err != nil { | ||
return err | ||
} | ||
|
||
continue | ||
madeDir[abs] = true | ||
case mode&os.ModeSymlink != 0: | ||
// TODO: ignore these for now. They were breaking x/build tests. | ||
// Implement these if/when we ever have a test that needs them. | ||
// But maybe we'd have to skip creating them on Windows for some builders | ||
// without permissions. | ||
default: | ||
return fmt.Errorf("tar file entry %s contained unsupported file type %v", f.Name, mode) | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
data, err := embeddedApp.ReadFile(currentDir + "/" + name) | ||
if err != nil { | ||
return err | ||
} | ||
// nativeRelPath verifies that p is a non-empty relative path | ||
// using either slashes or the buildlet's native path separator, | ||
// and returns it canonicalized to the native path separator. | ||
func nativeRelPath(p string) (string, error) { | ||
if p == "" { | ||
return "", errors.New("path not provided") | ||
} | ||
|
||
f := appPath + "/" + strings.TrimPrefix(currentDir, embedDir) + "/" + name | ||
if err := os.WriteFile(f, data, 0500); err != nil { | ||
return err | ||
if filepath.Separator != '/' && strings.Contains(p, string(filepath.Separator)) { | ||
clean := filepath.Clean(p) | ||
if filepath.IsAbs(clean) { | ||
return "", fmt.Errorf("path %q is not relative", p) | ||
} | ||
if clean == ".." || strings.HasPrefix(clean, ".."+string(filepath.Separator)) { | ||
return "", fmt.Errorf("path %q refers to a parent directory", p) | ||
} | ||
if strings.HasPrefix(p, string(filepath.Separator)) || filepath.VolumeName(clean) != "" { | ||
// On Windows, this catches semi-relative paths like "C:" (meaning “the | ||
// current working directory on volume C:”) and "\windows" (meaning “the | ||
// windows subdirectory of the current drive letter”). | ||
return "", fmt.Errorf("path %q is relative to volume", p) | ||
} | ||
return p, nil | ||
} | ||
|
||
return nil | ||
clean := path.Clean(p) | ||
if path.IsAbs(clean) { | ||
return "", fmt.Errorf("path %q is not relative", p) | ||
} | ||
if clean == ".." || strings.HasPrefix(clean, "../") { | ||
return "", fmt.Errorf("path %q refers to a parent directory", p) | ||
} | ||
canon := filepath.FromSlash(p) | ||
if filepath.VolumeName(canon) != "" { | ||
return "", fmt.Errorf("path %q begins with a native volume name", p) | ||
} | ||
return canon, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters