Skip to content

Instantly share code, notes, and snippets.

@rcrowley
Last active September 18, 2020 05:13
Show Gist options
  • Save rcrowley/9cc826addd549e515720e145c741d744 to your computer and use it in GitHub Desktop.
Save rcrowley/9cc826addd549e515720e145c741d744 to your computer and use it in GitHub Desktop.
// It seems that Go's archive/zip does not preserve the
// executable bit on input files on Mac OS. It does, so
// something else is fishy, but I'll preserve this for
// posterity and further thinking about what's wrong.
package zipadeedoodah
import (
"archive/zip"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"testing"
)
func TestZipExecutableBit(t *testing.T) {
// Run this test in a temporary directory and clean it up afterwards.
dirname, err := ioutil.TempDir("", "TestZipExecutableBit-")
if err != nil {
t.Fatal(err)
}
defer func() {
if err := os.RemoveAll(dirname); err != nil {
t.Fatal(err)
}
}()
// Create an empty executable file which we'll add to the archive to
// demonstrate the bug.
/*
f, err := os.OpenFile(filepath.Join(dirname, "bin.bin"), os.O_CREATE, 0777)
//f, err := os.Create(filepath.Join(dirname, "bin.bin")) // no x, test fails
if err != nil {
t.Fatal(err)
}
if err := f.Close(); err != nil {
t.Fatal(err)
}
*/
// Create an executable ELF binary that does nothing which we'll add to
// the archive to demonstrate the bug.
if err := ioutil.WriteFile(
filepath.Join(dirname, "bin.go"),
[]byte(`package main; func main() {}`),
0666,
); err != nil {
t.Fatal(err)
}
cmd := exec.Command("go", "mod", "init", "zipadeedoodah")
cmd.Dir = dirname
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
t.Fatal(err)
}
cmd = exec.Command("go", "build", "-o", "bin.bin")
cmd.Dir = dirname
cmd.Env = []string{"GOARCH=amd64", "GOOS=linux", "HOME=" + os.Getenv("HOME")}
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
t.Fatal(err)
}
/*
cmd = exec.Command(filepath.Join(dirname, "bin.bin"))
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil { // confirm it executes; only works on Linux
t.Fatal(err)
}
*/
// Create a zip file and add our empty executable to it.
f, err := os.Create(filepath.Join(dirname, "zip.zip"))
if err != nil {
t.Fatal(err)
}
w := zip.NewWriter(f)
fi, err := os.Stat(filepath.Join(dirname, "bin.bin"))
if err != nil {
t.Fatal(err)
}
h, err := zip.FileInfoHeader(fi)
if err != nil {
t.Fatal(err)
}
_, err = w.CreateHeader(h)
if err != nil {
t.Fatal(err)
}
// Close the zip file as pedantically as possible.
if err := w.Close(); err != nil {
t.Fatal(err)
}
if err := f.Close(); err != nil {
t.Fatal(err)
}
// Examine the zip file using the external toolchain.
/*
cmd = exec.Command("zipinfo", filepath.Join(dirname, "zip.zip"))
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
t.Fatal(err)
}
*/
// Open the zip file again, this time for reading.
r, err := zip.OpenReader(filepath.Join(dirname, "zip.zip"))
if err != nil {
t.Fatal(err)
}
defer func() {
if err := r.Close(); err != nil {
t.Fatal(err)
}
}()
// Assert that the empty executable in the zip file is executable and,
// indeed, has the same permissions as the original.
for _, f := range r.File {
if f.Name != "bin.bin" {
t.Fatal(f.Name)
}
if f.Mode().Perm()&0111 == 0 {
t.Fatalf("%o", f.Mode().Perm())
}
if f.Mode().Perm() != fi.Mode().Perm() {
t.Fatalf("%o", f.Mode().Perm())
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment