x/ref: Restructure services/mgmt/lib

The renamings:

services/mgmt/lib/binary/impl.go      -> services/binary/binarylib/client.go
services/mgmt/lib/binary/impl_test.go -> services/binary/binarylib/client_test.go

services/mgmt/lib/acls     -> services/internal/acls
services/mgmt/lib/fs       -> services/internal/fs
services/mgmt/lib/packages -> services/internal/packages
services/mgmt/lib/testutil -> services/internal/servicetest

Change-Id: Icabfbcc4e9a20fd5bf5e2a2761f98a016560d409
diff --git a/services/internal/packages/packages.go b/services/internal/packages/packages.go
new file mode 100644
index 0000000..3f9af83
--- /dev/null
+++ b/services/internal/packages/packages.go
@@ -0,0 +1,283 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package packages provides functionality to install ZIP and TAR packages.
+package packages
+
+import (
+	"archive/tar"
+	"archive/zip"
+	"compress/bzip2"
+	"compress/gzip"
+	"encoding/json"
+	"io"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"strings"
+
+	"v.io/v23/services/repository"
+	"v.io/v23/verror"
+)
+
+const (
+	defaultType    = "application/octet-stream"
+	createDirMode  = 0755
+	createFileMode = 0644
+)
+
+var typemap = map[string]repository.MediaInfo{
+	".zip":     repository.MediaInfo{Type: "application/zip"},
+	".tar":     repository.MediaInfo{Type: "application/x-tar"},
+	".tgz":     repository.MediaInfo{Type: "application/x-tar", Encoding: "gzip"},
+	".tar.gz":  repository.MediaInfo{Type: "application/x-tar", Encoding: "gzip"},
+	".tbz2":    repository.MediaInfo{Type: "application/x-tar", Encoding: "bzip2"},
+	".tb2":     repository.MediaInfo{Type: "application/x-tar", Encoding: "bzip2"},
+	".tbz":     repository.MediaInfo{Type: "application/x-tar", Encoding: "bzip2"},
+	".tar.bz2": repository.MediaInfo{Type: "application/x-tar", Encoding: "bzip2"},
+}
+
+const pkgPath = "v.io/x/ref/services/internal/packages"
+
+var (
+	errBadMediaType    = verror.Register(pkgPath+".errBadMediaType", verror.NoRetry, "{1:}{2:} unsupported media type{:_}")
+	errMkDirFailed     = verror.Register(pkgPath+".errMkDirFailed", verror.NoRetry, "{1:}{2:} os.Mkdir({3}) failed{:_}")
+	errFailedToExtract = verror.Register(pkgPath+".errFailedToExtract", verror.NoRetry, "{1:}{2:} failed to extract file {3} outside of install directory{:_}")
+	errBadFileSize     = verror.Register(pkgPath+".errBadFileSize", verror.NoRetry, "{1:}{2:} file size doesn't match for {3}: {4} != {5}{:_}")
+	errBadEncoding     = verror.Register(pkgPath+".errBadEncoding", verror.NoRetry, "{1:}{2:} unsupported encoding{:_}")
+)
+
+// MediaInfoForFileName returns the MediaInfo based on the file's extension.
+func MediaInfoForFileName(fileName string) repository.MediaInfo {
+	fileName = strings.ToLower(fileName)
+	for k, v := range typemap {
+		if strings.HasSuffix(fileName, k) {
+			return v
+		}
+	}
+	return repository.MediaInfo{Type: defaultType}
+}
+
+func copyFile(src, dst string) error {
+	s, err := os.Open(src)
+	if err != nil {
+		return err
+	}
+	defer s.Close()
+	d, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY, createFileMode)
+	if err != nil {
+		return err
+	}
+	defer d.Close()
+	if _, err = io.Copy(d, s); err != nil {
+		return err
+	}
+	return d.Sync()
+}
+
+// Install installs a package in the given destination. If the package is a TAR
+// or ZIP archive, the destination becomes a directory where the archive content
+// is extracted.  Otherwise, the package file itself is copied to the
+// destination.
+func Install(pkgFile, destination string) error {
+	mediaInfo, err := LoadMediaInfo(pkgFile)
+	if err != nil {
+		return err
+	}
+	switch mediaInfo.Type {
+	case "application/x-tar":
+		return extractTar(pkgFile, mediaInfo.Encoding, destination)
+	case "application/zip":
+		return extractZip(pkgFile, destination)
+	case defaultType:
+		return copyFile(pkgFile, destination)
+	default:
+		return verror.New(errBadMediaType, nil, mediaInfo.Type)
+	}
+}
+
+// LoadMediaInfo returns the MediaInfo for the given package file.
+func LoadMediaInfo(pkgFile string) (repository.MediaInfo, error) {
+	jInfo, err := ioutil.ReadFile(pkgFile + ".__info")
+	if err != nil {
+		return repository.MediaInfo{}, err
+	}
+	var info repository.MediaInfo
+	if err := json.Unmarshal(jInfo, &info); err != nil {
+		return repository.MediaInfo{}, err
+	}
+	return info, nil
+}
+
+// SaveMediaInfo saves the media info for a package.
+func SaveMediaInfo(pkgFile string, mediaInfo repository.MediaInfo) error {
+	jInfo, err := json.Marshal(mediaInfo)
+	if err != nil {
+		return err
+	}
+	infoFile := pkgFile + ".__info"
+	if err := ioutil.WriteFile(infoFile, jInfo, os.FileMode(0600)); err != nil {
+		return err
+	}
+	return nil
+}
+
+// CreateZip creates a package from the files in the source directory. The
+// created package is a Zip file.
+func CreateZip(zipFile, sourceDir string) error {
+	z, err := os.OpenFile(zipFile, os.O_CREATE|os.O_WRONLY, os.FileMode(0644))
+	if err != nil {
+		return err
+	}
+	defer z.Close()
+	w := zip.NewWriter(z)
+	if err := filepath.Walk(sourceDir, func(path string, info os.FileInfo, err error) error {
+		if err != nil {
+			return err
+		}
+		if sourceDir == path {
+			return nil
+		}
+		fh, err := zip.FileInfoHeader(info)
+		if err != nil {
+			return err
+		}
+		fh.Name, _ = filepath.Rel(sourceDir, path)
+		hdr, err := w.CreateHeader(fh)
+		if err != nil {
+			return err
+		}
+		if !info.IsDir() {
+			content, err := ioutil.ReadFile(path)
+			if err != nil {
+				return err
+			}
+			if _, err = hdr.Write(content); err != nil {
+				return err
+			}
+		}
+		return nil
+	}); err != nil {
+		return err
+	}
+	if err := w.Close(); err != nil {
+		return err
+	}
+	if err := SaveMediaInfo(zipFile, repository.MediaInfo{Type: "application/zip"}); err != nil {
+		return err
+	}
+	return nil
+}
+
+func extractZip(zipFile, installDir string) error {
+	if err := os.Mkdir(installDir, os.FileMode(createDirMode)); err != nil {
+		return verror.New(errMkDirFailed, nil, installDir, err)
+	}
+	zr, err := zip.OpenReader(zipFile)
+	if err != nil {
+		return err
+	}
+	for _, file := range zr.File {
+		fi := file.FileInfo()
+		name := filepath.Join(installDir, file.Name)
+		if !strings.HasPrefix(name, installDir) {
+			return verror.New(errFailedToExtract, nil, file.Name)
+		}
+		if fi.IsDir() {
+			if err := os.MkdirAll(name, os.FileMode(createDirMode)); err != nil && !os.IsExist(err) {
+				return err
+			}
+			continue
+		}
+		in, err := file.Open()
+		if err != nil {
+			return err
+		}
+		parentName := filepath.Dir(name)
+		if err := os.MkdirAll(parentName, os.FileMode(createDirMode)); err != nil {
+			return err
+		}
+		out, err := os.OpenFile(name, os.O_CREATE|os.O_WRONLY, os.FileMode(createFileMode))
+		if err != nil {
+			in.Close()
+			return err
+		}
+		nbytes, err := io.Copy(out, in)
+		in.Close()
+		out.Close()
+		if err != nil {
+			return err
+		}
+		if nbytes != fi.Size() {
+			return verror.New(errBadFileSize, nil, fi.Name(), nbytes, fi.Size())
+		}
+	}
+	return nil
+}
+
+func extractTar(pkgFile string, encoding string, installDir string) error {
+	if err := os.Mkdir(installDir, os.FileMode(createDirMode)); err != nil {
+		return verror.New(errMkDirFailed, nil, installDir, err)
+	}
+	f, err := os.Open(pkgFile)
+	if err != nil {
+		return err
+	}
+	defer f.Close()
+
+	var reader io.Reader
+	switch encoding {
+	case "":
+		reader = f
+	case "gzip":
+		var err error
+		if reader, err = gzip.NewReader(f); err != nil {
+			return err
+		}
+	case "bzip2":
+		reader = bzip2.NewReader(f)
+	default:
+		return verror.New(errBadEncoding, nil, encoding)
+	}
+
+	tr := tar.NewReader(reader)
+	for {
+		hdr, err := tr.Next()
+		if err == io.EOF {
+			return nil
+		}
+		if err != nil {
+			return err
+		}
+		name := filepath.Join(installDir, hdr.Name)
+		if !strings.HasPrefix(name, installDir) {
+			return verror.New(errFailedToExtract, nil, hdr.Name)
+		}
+		// Regular file
+		if hdr.Typeflag == tar.TypeReg {
+			out, err := os.OpenFile(name, os.O_CREATE|os.O_WRONLY, os.FileMode(createFileMode))
+			if err != nil {
+				return err
+			}
+			nbytes, err := io.Copy(out, tr)
+			out.Close()
+			if err != nil {
+				return err
+			}
+			if nbytes != hdr.Size {
+				return verror.New(errBadFileSize, nil, hdr.Name, nbytes, hdr.Size)
+			}
+			continue
+		}
+		// Directory
+		if hdr.Typeflag == tar.TypeDir {
+			if err := os.Mkdir(name, os.FileMode(createDirMode)); err != nil && !os.IsExist(err) {
+				return err
+			}
+			continue
+		}
+		// Skip unsupported types
+		// TODO(rthellend): Consider adding support for Symlink.
+	}
+}