misc/dist: better archive/tar Headers

This should live in archive/tar later (CL 5796073) but we
can always do that after Go 1 and stick it here for now.

R=golang-dev, adg
CC=golang-dev
https://golang.org/cl/5754096
diff --git a/misc/dist/bindist.go b/misc/dist/bindist.go
index 0d2fa21..9ffa028 100644
--- a/misc/dist/bindist.go
+++ b/misc/dist/bindist.go
@@ -26,7 +26,6 @@
 	"path/filepath"
 	"runtime"
 	"strings"
-	"syscall"
 )
 
 var (
@@ -527,22 +526,16 @@
 		if fi.IsDir() {
 			return nil
 		}
-		var typeFlag byte
-		switch {
-		case fi.Mode()&os.ModeType == 0:
-			typeFlag = tar.TypeReg
-		default:
-			log.Fatalf("makeTar: unknown file for file %q", name)
+		hdr, err := tarFileInfoHeader(fi, path)
+		if err != nil {
+			return err
 		}
-		hdr := &tar.Header{
-			Name:     name,
-			Mode:     int64(fi.Sys().(*syscall.Stat_t).Mode),
-			Size:     fi.Size(),
-			ModTime:  fi.ModTime(),
-			Typeflag: typeFlag,
-			Uname:    "root",
-			Gname:    "root",
-		}
+		hdr.Name = name
+		hdr.Uname = "root"
+		hdr.Gname = "root"
+		hdr.Uid = 0
+		hdr.Gid = 0
+
 		err = tw.WriteHeader(hdr)
 		if err != nil {
 			return fmt.Errorf("Error writing file %q: %v", name, err)
@@ -686,3 +679,64 @@
 	}
 	return
 }
+
+// sysStat, if non-nil, populates h from system-dependent fields of fi.
+var sysStat func(fi os.FileInfo, h *tar.Header) error
+
+// Mode constants from the tar spec.
+const (
+	c_ISDIR  = 040000
+	c_ISFIFO = 010000
+	c_ISREG  = 0100000
+	c_ISLNK  = 0120000
+	c_ISBLK  = 060000
+	c_ISCHR  = 020000
+	c_ISSOCK = 0140000
+)
+
+// tarFileInfoHeader creates a partially-populated Header from an os.FileInfo.
+// The filename parameter is used only in the case of symlinks, to call os.Readlink.
+// If fi is a symlink but filename is empty, an error is returned.
+func tarFileInfoHeader(fi os.FileInfo, filename string) (*tar.Header, error) {
+	h := &tar.Header{
+		Name:    fi.Name(),
+		ModTime: fi.ModTime(),
+		Mode:    int64(fi.Mode().Perm()), // or'd with c_IS* constants later
+	}
+	switch {
+	case fi.Mode()&os.ModeType == 0:
+		h.Mode |= c_ISREG
+		h.Typeflag = tar.TypeReg
+		h.Size = fi.Size()
+	case fi.IsDir():
+		h.Typeflag = tar.TypeDir
+		h.Mode |= c_ISDIR
+	case fi.Mode()&os.ModeSymlink != 0:
+		h.Typeflag = tar.TypeSymlink
+		h.Mode |= c_ISLNK
+		if filename == "" {
+			return h, fmt.Errorf("archive/tar: unable to populate Header.Linkname of symlinks")
+		}
+		targ, err := os.Readlink(filename)
+		if err != nil {
+			return h, err
+		}
+		h.Linkname = targ
+	case fi.Mode()&os.ModeDevice != 0:
+		if fi.Mode()&os.ModeCharDevice != 0 {
+			h.Mode |= c_ISCHR
+			h.Typeflag = tar.TypeChar
+		} else {
+			h.Mode |= c_ISBLK
+			h.Typeflag = tar.TypeBlock
+		}
+	case fi.Mode()&os.ModeSocket != 0:
+		h.Mode |= c_ISSOCK
+	default:
+		return nil, fmt.Errorf("archive/tar: unknown file mode %v", fi.Mode())
+	}
+	if sysStat != nil {
+		return h, sysStat(fi, h)
+	}
+	return h, nil
+}
diff --git a/misc/dist/stat_darwin.go b/misc/dist/stat_darwin.go
new file mode 100644
index 0000000..eb3f76a
--- /dev/null
+++ b/misc/dist/stat_darwin.go
@@ -0,0 +1,32 @@
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build darwin
+
+package main
+
+import (
+	"archive/tar"
+	"os"
+	"syscall"
+	"time"
+)
+
+func init() {
+	sysStat = func(fi os.FileInfo, h *tar.Header) error {
+		sys, ok := fi.Sys().(*syscall.Stat_t)
+		if !ok {
+			return nil
+		}
+		h.Uid = int(sys.Uid)
+		h.Gid = int(sys.Gid)
+		// TODO(bradfitz): populate username & group.  os/user
+		// doesn't cache LookupId lookups, and lacks group
+		// lookup functions.
+		h.AccessTime = time.Unix(sys.Atimespec.Unix())
+		h.ChangeTime = time.Unix(sys.Ctimespec.Unix())
+		// TODO(bradfitz): major/minor device numbers?
+		return nil
+	}
+}
diff --git a/misc/dist/stat_linux.go b/misc/dist/stat_linux.go
new file mode 100644
index 0000000..0ddb8a3
--- /dev/null
+++ b/misc/dist/stat_linux.go
@@ -0,0 +1,32 @@
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build linux
+
+package main
+
+import (
+	"archive/tar"
+	"os"
+	"syscall"
+	"time"
+)
+
+func init() {
+	sysStat = func(fi os.FileInfo, h *tar.Header) error {
+		sys, ok := fi.Sys().(*syscall.Stat_t)
+		if !ok {
+			return nil
+		}
+		h.Uid = int(sys.Uid)
+		h.Gid = int(sys.Gid)
+		// TODO(bradfitz): populate username & group.  os/user
+		// doesn't cache LookupId lookups, and lacks group
+		// lookup functions.
+		h.AccessTime = time.Unix(sys.Atim.Unix())
+		h.ChangeTime = time.Unix(sys.Ctim.Unix())
+		// TODO(bradfitz): major/minor device numbers?
+		return nil
+	}
+}