Merge "jiri: Support installation of base profile on fnl"
diff --git a/profiles/util.go b/profiles/util.go
index 3e6e3ff..4be9a35 100644
--- a/profiles/util.go
+++ b/profiles/util.go
@@ -5,10 +5,12 @@
 package profiles
 
 import (
+	"archive/zip"
 	"bufio"
 	"bytes"
 	"flag"
 	"fmt"
+	"net/http"
 	"os"
 	"path/filepath"
 	"runtime"
@@ -253,7 +255,30 @@
 	return nil
 }
 
+// Fetch downloads the specified url and saves it to dst.
+// TODO(nlacasse, cnicoloau): Move this to a package for profile-implementors
+// so it does not pollute the profile package namespace.
+func Fetch(ctx *tool.Context, dst, url string) error {
+	ctx.Run().Output([]string{"fetching " + url})
+	resp, err := http.Get(url)
+	if err != nil {
+		return err
+	}
+	defer resp.Body.Close()
+	if resp.StatusCode != http.StatusOK {
+		return fmt.Errorf("got non-200 status code while getting %v: %v", url, resp.StatusCode)
+	}
+	file, err := ctx.Run().Create(dst)
+	if err != nil {
+		return err
+	}
+	_, err = ctx.Run().Copy(file, resp.Body)
+	return err
+}
+
 // GitCloneRepo clones a repo at a specific revision in outDir.
+// TODO(nlacasse, cnicoloau): Move this to a package for profile-implementors
+// so it does not pollute the profile package namespace.
 func GitCloneRepo(ctx *tool.Context, remote, revision, outDir string, outDirPerm os.FileMode) (e error) {
 	cwd, err := os.Getwd()
 	if err != nil {
@@ -270,8 +295,45 @@
 	if err := ctx.Run().Chdir(outDir); err != nil {
 		return err
 	}
-	if err := ctx.Git().Reset(revision); err != nil {
+	return ctx.Git().Reset(revision)
+}
+
+// Unzip unzips the file in srcFile and puts resulting files in directory dstDir.
+// TODO(nlacasse, cnicoloau): Move this to a package for profile-implementors
+// so it does not pollute the profile package namespace.
+func Unzip(ctx *tool.Context, srcFile, dstDir string) error {
+	r, err := zip.OpenReader(srcFile)
+	if err != nil {
 		return err
 	}
+	defer r.Close()
+
+	unzipFn := func(zFile *zip.File) error {
+		rc, err := zFile.Open()
+		if err != nil {
+			return err
+		}
+		defer rc.Close()
+
+		fileDst := filepath.Join(dstDir, zFile.Name)
+		if zFile.FileInfo().IsDir() {
+			return ctx.Run().MkdirAll(fileDst, zFile.Mode())
+		}
+		file, err := ctx.Run().OpenFile(fileDst, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, zFile.Mode())
+		if err != nil {
+			return err
+		}
+		defer file.Close()
+		_, err = ctx.Run().Copy(file, rc)
+		return err
+	}
+
+	ctx.Run().Output([]string{"unzipping " + srcFile})
+	for _, zFile := range r.File {
+		ctx.Run().Output([]string{"extracting " + zFile.Name})
+		if err := unzipFn(zFile); err != nil {
+			return err
+		}
+	}
 	return nil
 }
diff --git a/runutil/wrapper.go b/runutil/wrapper.go
index 6a3a3f2..d4c63b3 100644
--- a/runutil/wrapper.go
+++ b/runutil/wrapper.go
@@ -4,12 +4,9 @@
 
 package runutil
 
-// TODO(jsimsa): Write wrappers for additional functions from the Go
-// standard libraries "os" and "ioutil" that our tools use: Chmod(),
-// Create(), OpenFile(), ...
-
 import (
 	"fmt"
+	"io"
 	"io/ioutil"
 	"os"
 	"os/exec"
@@ -57,6 +54,26 @@
 	return r.helper(func() error { return os.Chmod(dir, mode) }, fmt.Sprintf("chmod %v %q", mode, dir))
 }
 
+// Create is a wrapper around os.Create that handles options such as "verbose"
+// or "dry run".
+func (r *Run) Create(name string) (f *os.File, err error) {
+	r.helper(func() error {
+		f, err = os.Create(name)
+		return err
+	}, fmt.Sprintf("create %q", name))
+	return
+}
+
+// Copy is a wrapper around io.Copy that handles options such as "verbose" or
+// "dry run".
+func (r *Run) Copy(dst *os.File, src io.Reader) (n int64, err error) {
+	r.helper(func() error {
+		n, err = io.Copy(dst, src)
+		return err
+	}, fmt.Sprintf("io.copy %q", dst.Name()))
+	return
+}
+
 // MkdirAll is a wrapper around os.MkdirAll that handles options such
 // as "verbose" or "dry run".
 func (r *Run) MkdirAll(dir string, mode os.FileMode) error {
@@ -65,38 +82,42 @@
 
 // Open is a wrapper around os.Open that handles options such as
 // "verbose" or "dry run".
-func (r *Run) Open(name string) (*os.File, error) {
-	var file *os.File
-	var err error
+func (r *Run) Open(name string) (f *os.File, err error) {
 	r.helper(func() error {
-		file, err = os.Open(name)
+		f, err = os.Open(name)
 		return err
 	}, fmt.Sprintf("open %q", name))
-	return file, err
+	return
+}
+
+// OpenFile is a wrapper around os.OpenFile that handles options such as
+// "verbose" or "dry run".
+func (r *Run) OpenFile(name string, flag int, perm os.FileMode) (f *os.File, err error) {
+	r.helper(func() error {
+		f, err = os.OpenFile(name, flag, perm)
+		return err
+	}, fmt.Sprintf("open file %q", name))
+	return
 }
 
 // ReadDir is a wrapper around ioutil.ReadDir that handles options
 // such as "verbose" or "dry run".
-func (r *Run) ReadDir(dirname string) ([]os.FileInfo, error) {
-	var fileInfos []os.FileInfo
-	var err error
+func (r *Run) ReadDir(dirname string) (fileInfos []os.FileInfo, err error) {
 	r.dryRun(func() error {
 		fileInfos, err = ioutil.ReadDir(dirname)
 		return err
 	}, fmt.Sprintf("ls %q", dirname))
-	return fileInfos, err
+	return
 }
 
 // ReadFile is a wrapper around ioutil.ReadFile that handles options
 // such as "verbose" or "dry run".
-func (r *Run) ReadFile(filename string) ([]byte, error) {
-	var bytes []byte
-	var err error
+func (r *Run) ReadFile(filename string) (bytes []byte, err error) {
 	r.dryRun(func() error {
 		bytes, err = ioutil.ReadFile(filename)
 		return err
 	}, fmt.Sprintf("read %q", filename))
-	return bytes, err
+	return
 }
 
 // RemoveAll is a wrapper around os.RemoveAll that handles options
@@ -137,14 +158,12 @@
 
 // Stat is a wrapper around os.Stat that handles options such as
 // "verbose" or "dry run".
-func (r *Run) Stat(name string) (os.FileInfo, error) {
-	var fileInfo os.FileInfo
-	var err error
+func (r *Run) Stat(name string) (fileInfo os.FileInfo, err error) {
 	r.dryRun(func() error {
 		fileInfo, err = os.Stat(name)
 		return err
 	}, fmt.Sprintf("stat %q", name))
-	return fileInfo, err
+	return
 }
 
 // IsDir is a wrapper around os.Stat that handles options such as
@@ -170,24 +189,34 @@
 
 // TempDir is a wrapper around ioutil.TempDir that handles options
 // such as "verbose" or "dry run".
-func (r *Run) TempDir(dir, prefix string) (string, error) {
-	var err error
+func (r *Run) TempDir(dir, prefix string) (tmpDir string, err error) {
 	if dir == "" {
 		dir = os.Getenv("TMPDIR")
 	}
-	tmpDir := fmt.Sprintf("%v%c%vXXXXXX", dir, os.PathSeparator, prefix)
-	tmpDir = filepath.Clean(tmpDir)
+	tmpDir = filepath.Join(dir, prefix+"XXXXXX")
 	r.helper(func() error {
 		tmpDir, err = ioutil.TempDir(dir, prefix)
 		return err
 	}, fmt.Sprintf("mkdir -p %q", tmpDir))
-	return tmpDir, err
+	return
+}
+
+// TempFile is a wrapper around ioutil.TempFile that handles options
+// such as "verbose" or "dry run".
+func (r *Run) TempFile(dir, prefix string) (f *os.File, err error) {
+	r.helper(func() error {
+		f, err = ioutil.TempFile(dir, prefix)
+		return err
+	}, fmt.Sprintf("open %q", f.Name()))
+	return
 }
 
 // WriteFile is a wrapper around ioutil.WriteFile that handles options
 // such as "verbose" or "dry run".
 func (r *Run) WriteFile(filename string, data []byte, perm os.FileMode) error {
-	return r.helper(func() error { return ioutil.WriteFile(filename, data, perm) }, fmt.Sprintf("write %q", filename))
+	return r.helper(func() error {
+		return ioutil.WriteFile(filename, data, perm)
+	}, fmt.Sprintf("write %q", filename))
 }
 
 // DirectoryExists tests if a directory exists with appropriate logging.