Merge "ref: Rewrite depcop to check transitive dependencies."
diff --git a/cmd/principal/bless.go b/cmd/principal/bless.go
index aff6b1a..ef4e761 100644
--- a/cmd/principal/bless.go
+++ b/cmd/principal/bless.go
@@ -173,6 +173,7 @@
 <div class="well">{{.ErrLong}}</div>
 {{else}}
 <h3>Received blessings: <tt>{{.Blessings}}</tt></h3>
+<h4>You may close this tab now.</h4>
 {{end}}
 </div>
 </body>
diff --git a/cmd/vbash b/cmd/vbash
index 9802ef3..d136206 100755
--- a/cmd/vbash
+++ b/cmd/vbash
@@ -3,225 +3,6 @@
 # Use of this source code is governed by a BSD-style
 # license that can be found in the LICENSE file.
 #
-# Starts up a shell running under the security agent.
-#
-# Specifically:
-#
-# 1. Fetches the binaries needed to set up the security environment.  If it
-# can't fetch fresh binaries, it issues a warning but proceeds with existing
-# binaries, if any.
-#
-# 2. Starts a shell under the agent, optionally fetching a remote blessing if
-# the principal is missing.
-#
-# Uses ~/.vbash to store its files, including binaries and the principal.
-#
-# Usage:
-#
-# # Builds and fetches binaries from local repository
-# ./vbash
-#
-# # Gets binaries from local filesystem
-# ./vbash /path/to/binaries
-#
-# # Gets binaries from HTTP server
-# ./vbash http://host/path
-#
-# # Work with staging environment.
-# ./vbash -s
-#
-# Limitations:
-# Tested on goobuntu and OS X 10.9.5.
-
-set -e
-
-readonly BIN_PACKAGES=(v.io/x/ref/cmd/principal v.io/x/ref/services/agent/agentd)
-BIN_NAMES=(${BIN_PACKAGES[@]})
-for (( i=0; i<${#BIN_PACKAGES[@]}; i++ )); do
-  BIN_NAMES[$i]=$(basename "${BIN_PACKAGES[$i]}")
-done
-
-# TODO(caprita): share copy_binaries and get_binaries with nminstall.
-
-###############################################################################
-# Copies one binary from source to destination.
-# Arguments:
-#   name of the binary
-#   source dir of binary
-#   destination dir of binary
-# Returns:
-#   None
-###############################################################################
-copy_binary() {
-  local -r BIN_NAME="$1"
-  local -r BIN_SRC_DIR="$2"
-  local -r BIN_DEST_DIR="$3"
-  local -r SOURCE="${BIN_SRC_DIR}/${BIN_NAME}"
-  if [[ -x "${SOURCE}" ]]; then
-    local -r DESTINATION="${BIN_DEST_DIR}/${BIN_NAME}"
-    cp "${SOURCE}" "${DESTINATION}"
-    chmod 700 "${DESTINATION}"
-  else
-    echo "couldn't find ${SOURCE}"
-    return 1
-  fi
-}
-
-###############################################################################
-# Fetches binaries needed by device manager installation.
-# Globals:
-#   BIN_NAMES
-#   BIN_PACKAGES
-#   V23_ROOT
-# Arguments:
-#   destination for binaries
-#   source of binaries
-# Returns:
-#   None
-###############################################################################
-get_binaries() {
-  local -r BIN_INSTALL="$1"
-  local -r BIN_SOURCE="$2"
-
-  local bin_names_str=""
-  for bin_name in "${BIN_NAMES[@]}"; do
-    bin_names_str+=" ${bin_name}"
-  done
-
-  # If source is not specified, try to build latest version of the binaries and copy
-  # them from the repository.
-  if [[ -z "${BIN_SOURCE}" ]]; then
-    if [[ -z "${V23_ROOT}" ]]; then
-      echo 'WARNING: V23_ROOT is not specified, cannot build fresh binaries'
-      return
-    fi
-    local -r REPO_BIN_DIR="${V23_ROOT}/release/go/bin"
-    echo "Building and Fetching binaries:${bin_names_str} from build repository: ${REPO_BIN_DIR} ..."
-    for package in "${BIN_PACKAGES[@]}"; do
-       local bin_name=$(basename "${package}")
-       v23 go install "${package}" 2> /dev/null || echo "WARNING: Could not build binary: ${bin_name}"
-       # while the build failed, we still try to see if we can copy the binary from the build repository.
-       copy_binary "${bin_name}" "${REPO_BIN_DIR}" "${BIN_INSTALL}" || echo "WARNING: Could not copy binary: ${bin_name} from build repository: ${REPO_BIN_DIR}"
-    done
-    return
-  fi
-
-  # If the source is specified as an existing local filesystem path,
-  # look for the binaries there.
-  if [[ -d "${BIN_SOURCE}" ]]; then
-      echo "Fetching binaries:${bin_names_str} locally from: ${BIN_SOURCE} ..."
-      for bin_name in "${BIN_NAMES[@]}"; do
-        copy_binary "${bin_name}" "${BIN_SOURCE}" "${BIN_INSTALL}" || echo "WARNING: Could not copy binary: ${bin_name} from: ${BIN_SOURCE}"
-      done
-      return
-  fi
-
-  # If the source looks like a URL, use HTTP to fetch.
-  local -r URL_REGEXP='^(https?|ftp|file)://'
-  if [[ "${BIN_SOURCE}" =~ ${URL_REGEXP} ]]; then
-    echo "Fetching binaries:${bin_names_str} remotely from: ${BIN_SOURCE} ..."
-      for bin_name in "${BIN_NAMES[@]}"; do
-        local DEST="${BIN_INSTALL}/${bin_name}"
-        (curl -f -o "${DEST}" "${BIN_SOURCE}/${bin_name}" && chmod 700 "${DEST}") || echo "WARNING: Could not fetch binary: ${bin_name} from HTTP server: ${BIN_SOURCE}/${bin_name}"
-      done
-      return
-  fi
-
-  echo "WARNING: couldn't fetch binaries."
-}
-
-main() {
-  if [[ ! -z "${VBASH_INDICATOR}" ]]; then
-    echo "Disallowing running VBASH within VBASH."
-    echo "https://memegen.googleplex.com/5551020600983552"
-    exit 1
-  fi
-  export VBASH_INDICATOR="1"
-
-  # Check staging flag "-s".
-  local INSTALL_DIR="${HOME}/.vbash"
-  local IDENTITY_PROVIDER="https://dev.v.io/auth/google"
-  local NS="/(dev.v.io/role/vprod)@ns.dev.v.io:8101"
-  local TAG=""
-  local TITLE="vbash"
-  while getopts ":s" opt; do
-    case $opt in
-      s)
-        INSTALL_DIR="${HOME}/.vbash-staging"
-        IDENTITY_PROVIDER="https://dev.staging.v.io/auth/google"
-        NS="/(dev.v.io/role/vprod)@ns.dev.staging.v.io:8101"
-        TAG="\[\e[1;35m\][STAGING] "
-        TITLE="vbash staging"
-        ;;
-      \?)
-        echo "Invalid option: -$OPTARG" >&2
-        exit 1
-        ;;
-    esac
-  done
-  shift $(( OPTIND - 1 ))
-
-  if [[ ! -e "${INSTALL_DIR}" ]]; then
-    mkdir -m 700 "${INSTALL_DIR}"
-  fi
-  if [[ ! -d "${INSTALL_DIR}" ]]; then
-    echo "${INSTALL_DIR} is not a directory!"
-    exit 1
-  fi
-
-  local -r BIN_INSTALL="${INSTALL_DIR}/bin"
-  if [[ ! -e "${BIN_INSTALL}" ]]; then
-    mkdir -m 700 "${BIN_INSTALL}"
-  fi
-  if [[ ! -d "${BIN_INSTALL}" ]]; then
-    echo "${BIN_INSTALL} is not a directory!"
-    exit 1
-  fi
-
-  # Fetch the binaries.
-  local -r BIN_SOURCE="$1"
-  get_binaries "${BIN_INSTALL}" "${BIN_SOURCE}"
-  for bin_name in "${BIN_NAMES[@]}"; do
-    local BINARY="${BIN_INSTALL}/${bin_name}"
-    if [[ ! -s "${BINARY}" ]]; then
-      echo "${BINARY} is empty."
-      exit 1
-    fi
-  done
-  echo "Using binaries in ${BIN_INSTALL}."
-
-  # Set up the script to be run by the agent.  It first optionally seeks a
-  # blessing, and then runs an interactive shell.
-
-  local -r CREDENTIALS_DIR="${INSTALL_DIR}/principal"
-  if [[ ! -d "${CREDENTIALS_DIR}" ]]; then
-    SEEK_BLESSING="1"
-  fi
-
-  # Use a custom rcfile to optionally get a blessing and also to modify the
-  # shell prompt to include the default vanadium blessing.
-  cat <<EOF > "${INSTALL_DIR}/rcfile"
-if [[ Darwin == "$(uname)" ]]; then
-  if [[ -r /etc/profile ]]; then . /etc/profile; fi
-  for n in ~/.bash_profile ~/.bash_login ~/.profile; do
-    if [[ -r "$n" ]]; then
-      . "$n"
-      break
-    fi
-  done
-else
-  if [[ -f ~/.bashrc ]]; then . ~/.bashrc; fi
-fi
-export PROMPT_COMMAND='PS1="\[\e]0;${TITLE}\007\]\u@\h (${TAG}\[\e[1;32m\]\$("${BIN_INSTALL}/principal" dump -s)\[\e[0m\]):\w \$ "'
-export V23_NAMESPACE="${NS}"
-export V23_OAUTH_IDENTITY_PROVIDER="${IDENTITY_PROVIDER}"
-if [[ "${SEEK_BLESSING}" -eq "1" ]]; then
-  "${BIN_INSTALL}/principal" seekblessings
-fi
-
-EOF
-
-V23_CREDENTIALS="${CREDENTIALS_DIR}" exec "${BIN_INSTALL}/agentd" --additional-principals="${CREDENTIALS_DIR}" bash --rcfile "${INSTALL_DIR}/rcfile"
-}
-
-main "$@"
+# Tombstone for the vbash script after move.
+# TODO(caprita): Remove this on 4/29/15.
+echo "vbash has moved to ${V23_ROOT}/infrastructure/vanadium/scripts/vbash"
diff --git a/cmd/vom/vom.go b/cmd/vom/vom.go
index d8d82a0..1c7398f 100644
--- a/cmd/vom/vom.go
+++ b/cmd/vom/vom.go
@@ -273,5 +273,5 @@
 }
 
 func (w dumpWriter) WriteStatus(status vom.DumpStatus) {
-	w.status.Write([]byte("\n" + status.String() + "\n"))
+	w.status.Write([]byte(status.String() + "\n"))
 }
diff --git a/profiles/internal/naming/endpoint.go b/profiles/internal/naming/endpoint.go
index 74b1b2b..5190d82 100644
--- a/profiles/internal/naming/endpoint.go
+++ b/profiles/internal/naming/endpoint.go
@@ -117,7 +117,10 @@
 		ep.Protocol = naming.UnknownProtocol
 	}
 
-	ep.Address = parts[2]
+	var err error
+	if ep.Address, err = unescape(parts[2]); err != nil {
+		return fmt.Errorf("invalid address: %v", err)
+	}
 	if len(ep.Address) == 0 {
 		ep.Address = net.JoinHostPort("", "0")
 	}
@@ -126,7 +129,6 @@
 		return fmt.Errorf("invalid routing id: %v", err)
 	}
 
-	var err error
 	if ep.IsMountTable, ep.IsLeaf, err = parseMountTableFlag(parts[4]); err != nil {
 		return fmt.Errorf("invalid mount table flag: %v", err)
 	}
@@ -162,7 +164,7 @@
 		}
 		blessings := strings.Join(ep.Blessings, blessingsSeparator)
 		return fmt.Sprintf("@5@%s@%s@%s@%s@%s@@",
-			ep.Protocol, ep.Address, ep.RID, mt, blessings)
+			ep.Protocol, escape(ep.Address), ep.RID, mt, blessings)
 	}
 }
 
@@ -207,3 +209,72 @@
 func (a *addr) String() string {
 	return a.address
 }
+
+func escape(s string) string {
+	if !strings.ContainsAny(s, "%@") {
+		return s
+	}
+	t := make([]byte, len(s)*3)
+	j := 0
+	for i := 0; i < len(s); i++ {
+		switch c := s[i]; c {
+		case '@', '%':
+			t[j] = '%'
+			t[j+1] = "0123456789ABCDEF"[c>>4]
+			t[j+2] = "0123456789ABCDEF"[c&15]
+			j += 3
+		default:
+			t[j] = c
+			j++
+		}
+	}
+	return string(t[:j])
+}
+
+func ishex(c byte) bool {
+	switch {
+	case '0' <= c && c <= '9':
+		return true
+	case 'a' <= c && c <= 'f':
+		return true
+	case 'A' <= c && c <= 'F':
+		return true
+	}
+	return false
+}
+
+func unhex(c byte) byte {
+	switch {
+	case '0' <= c && c <= '9':
+		return c - '0'
+	case 'a' <= c && c <= 'f':
+		return c - 'a' + 10
+	case 'A' <= c && c <= 'F':
+		return c - 'A' + 10
+	}
+	return 0
+}
+
+func unescape(s string) (string, error) {
+	if !strings.Contains(s, "%") {
+		return s, nil
+	}
+	t := make([]byte, len(s))
+	j := 0
+	for i := 0; i < len(s); {
+		switch s[i] {
+		case '%':
+			if len(s) <= i+2 || !ishex(s[i+1]) || !ishex(s[i+2]) {
+				return s, fmt.Errorf("invalid escape %q", s)
+			}
+			t[j] = unhex(s[i+1])<<4 | unhex(s[i+2])
+			j++
+			i += 3
+		default:
+			t[j] = s[i]
+			j++
+			i++
+		}
+	}
+	return string(t[:j]), nil
+}
diff --git a/profiles/internal/naming/endpoint_test.go b/profiles/internal/naming/endpoint_test.go
index 6c4fdb6..f1d6d56 100644
--- a/profiles/internal/naming/endpoint_test.go
+++ b/profiles/internal/naming/endpoint_test.go
@@ -92,7 +92,7 @@
 		}
 		ep, err := NewEndpoint(test.String)
 		if err != nil {
-			t.Errorf("Test %d: Endpoint(%q) failed with %v", i, test.String, err)
+			t.Errorf("Test %d: NewEndpoint(%q) failed with %v", i, test.String, err)
 			continue
 		}
 		if !reflect.DeepEqual(ep, test.Endpoint) {
@@ -179,3 +179,25 @@
 		}
 	}
 }
+
+func TestEscapeEndpoint(t *testing.T) {
+	defver := defaultVersion
+	defer func() {
+		defaultVersion = defver
+	}()
+	testcases := []naming.Endpoint{
+		&Endpoint{Protocol: "unix", Address: "@", RID: naming.FixedRoutingID(0xdabbad00)},
+		&Endpoint{Protocol: "unix", Address: "@/%", RID: naming.FixedRoutingID(0xdabbad00)},
+	}
+	for i, ep := range testcases {
+		epstr := ep.String()
+		got, err := NewEndpoint(epstr)
+		if err != nil {
+			t.Errorf("Test %d: NewEndpoint(%q) failed with %v", i, epstr, err)
+			continue
+		}
+		if !reflect.DeepEqual(ep, got) {
+			t.Errorf("Test %d: Got endpoint %#v, want %#v", i, got, ep)
+		}
+	}
+}
diff --git a/services/agent/internal/cache/cache.go b/services/agent/internal/cache/cache.go
index 252b6a3..e0e7026 100644
--- a/services/agent/internal/cache/cache.go
+++ b/services/agent/internal/cache/cache.go
@@ -380,7 +380,10 @@
 		for {
 			if recvErr := call.Recv(&x); recvErr != nil {
 				if ctx.Err() != context.Canceled {
-					vlog.Infof("Error from agent: %v", recvErr)
+					// TODO(mattr): For now I'm commenting this out since the context
+					// doesn't get cancelled before the client is closed.
+					// Revisit this.
+					vlog.VI(2).Infof("Error from agent: %v", recvErr)
 				}
 				flush()
 				call.Finish()
diff --git a/services/identity/internal/server/identityd.go b/services/identity/internal/server/identityd.go
index ffa10ef..80c8f8c 100644
--- a/services/identity/internal/server/identityd.go
+++ b/services/identity/internal/server/identityd.go
@@ -151,6 +151,10 @@
 
 	externalHttpAddr = httpAddress(externalHttpAddr, httpAddr)
 
+	http.HandleFunc("/health", func(w http.ResponseWriter, _ *http.Request) {
+		w.WriteHeader(http.StatusNoContent)
+	})
+
 	n := "/auth/google/"
 	args := oauth.HandlerArgs{
 		Principal:               principal,