Import changes for goma server

  - a702111367735f8c0a46225ea9146dc57b3ba713 Should refer env_file_for_docker in WORK_DIR instead of c...
  - 9ee4ac3baa2bfe9a6c51292aa04763b31a6371a9 remoteexec: ExecuteAndWait returns error of resp status.
  - 18366bdf014af5e6e7cb79ed274ac86ad90182f0 remoteexec: don't add . for wrapper on windows
  - 7ee036e7a320085d751425b91f5fafd2949496bb remoteexec: fix retry by ExecuteResponse status
  - bc9eb1ff4b5995e32bedc0626580c1322b21dab2 roll cloud.google.com/go from 0.41.0 to 0.43.0
  - 4bc2b91a156363be0d2404c150fd787d808d375d remoteexec: update request metadata name
  - c670f55ebb2dd5f2cb9ae58b406109f6424a212c User environment should directly goes to where the compil...
  - e839bb6deafc14cc6ddbd2390a9f77923b85ed34 roll github.com/golang/protobuf from 1.3.1 to 1.3.2
  - c8c02f1a5bd9f80f73dffd1d6599a8288d7daac3 use go 1.12.7
  - 8c85c6baeac7addfda80134578d2ac5304837ab7 Use "pwd -P" instead of pwd to get current working direct...
  - b2d77a97fd319e5bec7e8de559a79fd895bbc018 roll google.golang.org/grpc from 1.21.1 to 1.22.0
  - a506584bd95b470c908698c1be2ecb94d2620aa0 roll cloud.google.com/go from 0.40.0 to 0.41.0
  - 96a8f21ee37900a200b3e7021d7b3cdb95e17035 remoteexec: reduce log volume
  - c713279d342b08cffe62fb266c6890c9fd46598b remoteexec: set rpc id
  - 8640543157f9b1cab6be2e9b5970101c248f0329 remoteexec_proxy: add document how to deploy it on appeng...
  - f9dc9f386b2dc522458f68e71af052f88a184f00 nsjail: add dummy HOME directory.
  - 64808c917652706de338b858213626dda987b17c nsjail: make /dev/null available.
  - f0a4ba0be368a4edcbd259193d725b8700418914 nsjail: set up PATH env from ExecReq::ToolchainSpec.
  - f1a01e793953c325c69576f75af614b9bbe03174 roll google.golang.org/api from 0.6.0 to 0.7.0

GitOrigin-RevId: 509e19eb587f2a5deb66290e1e70b5665b9a50d5
Change-Id: I7c08d57a3bbe51ff6c199228308ff13c0f320a49
TBR=yyanagisawa@google.com, tikuta@google.com
diff --git a/cipd_manifest.txt b/cipd_manifest.txt
index 847d7a1..9ee7676 100644
--- a/cipd_manifest.txt
+++ b/cipd_manifest.txt
@@ -13,7 +13,7 @@
 # https://chrome-infra-packages.appspot.com/
 
 # go
-infra/go/${platform} version:1.12.5
+infra/go/${platform} version:1.12.7
 
 # protoc
 # If the version you want is missing, please follow the instruction in:
diff --git a/cipd_manifest.versions b/cipd_manifest.versions
index 6dc1428..aa8ddf8 100644
--- a/cipd_manifest.versions
+++ b/cipd_manifest.versions
@@ -2,8 +2,8 @@
 # Do not modify manually. All changes will be overwritten.
 
 infra/go/linux-amd64
-	version:1.12.5
-	G8oyAmKE671-BJ-Mb1HdgChT7kkcoNwO-w_Z-vVnBGMC
+	version:1.12.7
+	lBm3oqkMtwrbllgPo-1cOqivkJcZpRUY-hO74LduBdMC
 
 infra/tools/protoc/linux-amd64
 	protobuf_version:v3.7.0
diff --git a/cmd/remoteexec_proxy/README.md b/cmd/remoteexec_proxy/README.md
new file mode 100644
index 0000000..c89c66c
--- /dev/null
+++ b/cmd/remoteexec_proxy/README.md
@@ -0,0 +1,105 @@
+# Goma server - remoteexec_proxy
+
+# How to deploy it on Google App Engine Flexible Environment
+
+`remoteexec_proxy` can run on Google App Engine Flexible Environment.
+
+## Dependencies
+
+Need to setup [cloud memorystore](https://cloud.google.com/memorystore/)
+and [cloud storage](https://cloud.google.com/storage/).
+
+### Cloud Memorystore
+
+Cloud memorystore is used for digest cache.
+You need to create an instance in the same location as App Engine apps.
+
+NOTE If different location, your server will get many `context deadline exceeded` errors.
+
+If not created, run `gcloud app create` can create App Engine app in
+the specific region in the cloud project.
+
+```
+$ gcloud app create --region=$REGION
+```
+
+If it is already created, you can check it by `gcloud app describe`
+
+```
+$ gcloud app describe --format='get(locationId)'
+```
+
+It is sufficient with 1GB size, but you might want to set
+`maxmemory-policy: allkeys-lru`.
+
+```
+$ gcloud redis instances create $REDIS_INSTANCE_NAME \
+ --region=$REGION \
+ --redis-config='maxmemory-poclicy=allkeys-lru'
+```
+
+### Cloud Storage
+
+Cloud storage is used to store file cache.
+
+```
+$ gsutil mb gs://${PROJECT_ID}-file-cache
+$ gsutil lifecycle set '{"rule": [{"action": {"type": "Delete"}, "condition": {"age": 1}}]}' gs://${PROJECT_ID}-file-cache
+```
+
+## Deploy
+
+To deploy the server, create a workspace
+
+```
+$ mkdir /path/to/workspace
+$ cd /path/to/workspace
+$ TOPDIR=$(pwd)
+$ git clone https://chromium.googlesource.com/infra/goma/server
+$ cd server
+$ GO111MODULE=on go mod vendor
+$ mkdir ../gopath/src
+$ export GOPATH=$(TOPDIR)/gopath
+$ mv vendor/* ../gopath/src
+$ mkdir app
+$ cd app
+$ cp ../server/cmd/remoteexec_server/* .
+$ REDISHOST=$(gcloud redis instances describe $REDIS_INSTANCE_NAME \
+   --region $REGION --format='get(host)')
+$ REDISPORT=$(gcloud redis instances describe $REDIS_INSTANCE_NAME \
+   --region $REGION --format='get(port)')
+$ cat > app.yaml <<EOF
+runtime: go
+env: flex
+
+network:
+  name: default
+
+liveness_check:
+  path: "/healthz"
+
+readiness_check:
+  path: "/healthz"
+
+env_variables:
+  REDISHOST: "$REDISHOST"
+  REDISPORT: "$REDISPORT"
+EOF
+$ cat > flag.go << EOF
+package main
+
+func init() {
+	*port = 8080
+	*remoteexecAddr = "remotebuildexecution.googleapis.com:443"
+	*remoteInstanceName = "projects/$PROJECT_ID/instances/default_instance"
+	*whitelistedUsers = "<comma separated whitelisted-users-email-address>"
+	*platformContainerImage = "docker://gcr.io/..."
+	*fileCacheBucket = "$PROJECT_ID-file-cache"
+}
+EOF
+$ gcloud app deploy
+```
+
+Then, you can use `$PROJECT_ID.appspot.com` as `$GOMA_SERVER_HOST`.
+You need to specify `GOMA_ARBTRARY_TOOLCHAIN_SUPPORT=true`.
+
diff --git a/cmd/remoteexec_proxy/main.go b/cmd/remoteexec_proxy/main.go
index 8bce206..886127d 100644
--- a/cmd/remoteexec_proxy/main.go
+++ b/cmd/remoteexec_proxy/main.go
@@ -54,6 +54,7 @@
 	filepb "go.chromium.org/goma/server/proto/file"
 	"go.chromium.org/goma/server/remoteexec"
 	"go.chromium.org/goma/server/remoteexec/digest"
+	"go.chromium.org/goma/server/rpc"
 	"go.chromium.org/goma/server/server"
 )
 
@@ -155,6 +156,35 @@
 	}, nil
 }
 
+type reExecServer struct {
+	re *remoteexec.Adapter
+}
+
+func (r reExecServer) Exec(ctx context.Context, req *gomapb.ExecReq) (*gomapb.ExecResp, error) {
+	ctx, id := rpc.TagID(ctx, req.GetRequesterInfo())
+	logger := log.FromContext(ctx)
+	logger.Infof("call exec %s", id)
+	return r.re.Exec(ctx, req)
+}
+
+type reFileServer struct {
+	s filepb.FileServiceServer
+}
+
+func (r reFileServer) StoreFile(ctx context.Context, req *gomapb.StoreFileReq) (*gomapb.StoreFileResp, error) {
+	ctx, id := rpc.TagID(ctx, req.GetRequesterInfo())
+	logger := log.FromContext(ctx)
+	logger.Infof("call storefile %s", id)
+	return r.s.StoreFile(ctx, req)
+}
+
+func (r reFileServer) LookupFile(ctx context.Context, req *gomapb.LookupFileReq) (*gomapb.LookupFileResp, error) {
+	ctx, id := rpc.TagID(ctx, req.GetRequesterInfo())
+	logger := log.FromContext(ctx)
+	logger.Infof("call lookupfile %s", id)
+	return r.s.LookupFile(ctx, req)
+}
+
 type localBackend struct {
 	ExecService execpb.ExecServiceServer
 	FileService filepb.FileServiceServer
@@ -387,8 +417,8 @@
 	mux := http.DefaultServeMux
 	frontend.Register(mux, frontend.Frontend{
 		Backend: localBackend{
-			ExecService: re,
-			FileService: fileServiceClient.Service,
+			ExecService: reExecServer{re},
+			FileService: reFileServer{fileServiceClient.Service},
 			Auth: &auth.Auth{
 				Client: authClient{Service: authService},
 			},
diff --git a/go.mod b/go.mod
index adebda3..da967e7 100644
--- a/go.mod
+++ b/go.mod
@@ -3,18 +3,15 @@
 go 1.12
 
 require (
-	cloud.google.com/go v0.40.0
+	cloud.google.com/go v0.43.0
 	contrib.go.opencensus.io/exporter/stackdriver v0.12.2
 	github.com/bazelbuild/remote-apis v0.0.0-20190606163526-a5c577357528
 	github.com/fsnotify/fsnotify v1.4.7
 	github.com/gogo/protobuf v1.2.1 // indirect
 	github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef
-	github.com/golang/mock v1.3.1 // indirect
-	github.com/golang/protobuf v1.3.1
+	github.com/golang/protobuf v1.3.2
 	github.com/gomodule/redigo v2.0.0+incompatible
-	github.com/google/btree v1.0.0 // indirect
 	github.com/google/go-cmp v0.3.0
-	github.com/google/pprof v0.0.0-20190515194954-54271f7e092f // indirect
 	github.com/google/uuid v1.1.1
 	github.com/googleapis/gax-go/v2 v2.0.5
 	github.com/googleapis/google-cloud-go-testing v0.0.0-20190307174402-f55056552511
@@ -28,19 +25,14 @@
 	go.uber.org/zap v1.10.0
 	golang.org/x/build v0.0.0-20190314215453-3ce8d48fad73
 	golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443 // indirect
-	golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522 // indirect
 	golang.org/x/image v0.0.0-20190618124811-92942e4437e2 // indirect
 	golang.org/x/mobile v0.0.0-20190607214518-6fa95d984e88 // indirect
 	golang.org/x/mod v0.1.0 // indirect
 	golang.org/x/net v0.0.0-20190620200207-3b0461eec859
 	golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
 	golang.org/x/sync v0.0.0-20190423024810-112230192c58
-	golang.org/x/sys v0.0.0-20190620070143-6f217b454f45 // indirect
-	golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect
-	golang.org/x/tools v0.0.0-20190620191750-1fa568393b23 // indirect
-	google.golang.org/api v0.6.0
-	google.golang.org/appengine v1.6.1 // indirect
-	google.golang.org/genproto v0.0.0-20190620144150-6af8c5fc6601
-	google.golang.org/grpc v1.21.1
+	google.golang.org/api v0.7.0
+	google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610
+	google.golang.org/grpc v1.22.0
 	honnef.co/go/tools v0.0.0-20190614002413-cb51c254f01b // indirect
 )
diff --git a/go.sum b/go.sum
index 76857ab..6c2ca7f 100644
--- a/go.sum
+++ b/go.sum
@@ -8,6 +8,10 @@
 cloud.google.com/go v0.39.0/go.mod h1:rVLT6fkc8chs9sfPtFc1SBH6em7n+ZoXaG+87tDISts=
 cloud.google.com/go v0.40.0 h1:FjSY7bOj+WzJe6TZRVtXI2b9kAYvtNg4lMbcH2+MUkk=
 cloud.google.com/go v0.40.0/go.mod h1:Tk58MuI9rbLMKlAjeO/bDnteAx7tX2gJIXw4T5Jwlro=
+cloud.google.com/go v0.41.0 h1:NFvqUTDnSNYPX5oReekmB+D+90jrJIcVImxQ3qrBVgM=
+cloud.google.com/go v0.41.0/go.mod h1:OauMR7DV8fzvZIl2qg6rkaIhD/vmgk4iwEw/h6ercmg=
+cloud.google.com/go v0.43.0 h1:banaiRPAM8kUVYneOSkhgcDsLzEvL25FinuiSZaH/2w=
+cloud.google.com/go v0.43.0/go.mod h1:BOSR3VbTLkk6FDC/TcffxP4NF/FFBGA5ku+jvKOP7pg=
 contrib.go.opencensus.io/exporter/stackdriver v0.12.1 h1:Dll2uFfOVI3fa8UzsHyP6z0M6fEc9ZTAMo+Y3z282Xg=
 contrib.go.opencensus.io/exporter/stackdriver v0.12.1/go.mod h1:iwB6wGarfphGGe/e5CWqyUk/cLzKnWsOKPVW3no6OTw=
 contrib.go.opencensus.io/exporter/stackdriver v0.12.2 h1:jU1p9F07ASK11wYgSTPKtFlTvTtCDj6R1d3nRt0ZHDE=
@@ -59,6 +63,8 @@
 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
 github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0=
 github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
 github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
@@ -242,6 +248,8 @@
 golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190620070143-6f217b454f45 h1:Dl2hc890lrizvUppGbRWhnIh2f8jOTCQpY5IKWRS0oM=
 golang.org/x/sys v0.0.0-20190620070143-6f217b454f45/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0 h1:HyfiK1WMnHj5FXFXatD+Qs1A/xC2Run6RzeW1SyHxpc=
+golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2 h1:z99zHgr7hKfrUcX/KsoJk5FJfjTceCKIp96+biqP4To=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -262,9 +270,12 @@
 golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 golang.org/x/tools v0.0.0-20190530171427-2b03ca6e44eb/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 golang.org/x/tools v0.0.0-20190620191750-1fa568393b23/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190624190245-7f2218787638/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
 google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
 google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
@@ -274,6 +285,8 @@
 google.golang.org/api v0.5.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
 google.golang.org/api v0.6.0 h1:2tJEkRfnZL5g1GeBUlITh/rqT5HG3sFcoVCUUxmgJ2g=
 google.golang.org/api v0.6.0/go.mod h1:btoxGiFvQNVUZQ8W08zLtrVS08CNpINPEfxXxgJL1Q4=
+google.golang.org/api v0.7.0 h1:9sdfJOzWlkqPltHAuzT2Cp+yrBeY1KRVYgms8soxMwM=
+google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -303,6 +316,10 @@
 google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
 google.golang.org/genproto v0.0.0-20190620144150-6af8c5fc6601 h1:9VBRTdmgQxbs6HE0sUnMrSWNePppAJU07NYvX5dIB04=
 google.golang.org/genproto v0.0.0-20190620144150-6af8c5fc6601/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
+google.golang.org/genproto v0.0.0-20190626174449-989357319d63 h1:UsSJe9fhWNSz6emfIGPpH5DF23t7ALo2Pf3sC+/hsdg=
+google.golang.org/genproto v0.0.0-20190626174449-989357319d63/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
+google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610 h1:Ygq9/SRJX9+dU0WCIICM8RkWvDw03lvB77hrhJnpxfU=
+google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
 google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
 google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
 google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
@@ -314,6 +331,8 @@
 google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
 google.golang.org/grpc v1.21.1 h1:j6XxA85m/6txkUCHvzlV5f+HBNl/1r5cZ2A/3IEFOO8=
 google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
+google.golang.org/grpc v1.22.0 h1:J0UbZOIrCAl+fpTOf8YLs4dJo8L/owV4LYVtAXQoPkw=
+google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
@@ -326,6 +345,7 @@
 honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190215041234-466a0476246c/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190614002413-cb51c254f01b/go.mod h1:JlmFZigtG9vBVR3QGIQ9g/Usz4BzH+Xm6Z8iHQWRYUw=
 rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
 sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
diff --git a/proto/auth/auth_service.pb.go b/proto/auth/auth_service.pb.go
index 2574196..827165d 100644
--- a/proto/auth/auth_service.pb.go
+++ b/proto/auth/auth_service.pb.go
@@ -8,6 +8,8 @@
 	fmt "fmt"
 	proto "github.com/golang/protobuf/proto"
 	grpc "google.golang.org/grpc"
+	codes "google.golang.org/grpc/codes"
+	status "google.golang.org/grpc/status"
 	math "math"
 )
 
@@ -71,6 +73,14 @@
 	Auth(context.Context, *AuthReq) (*AuthResp, error)
 }
 
+// UnimplementedAuthServiceServer can be embedded to have forward compatible implementations.
+type UnimplementedAuthServiceServer struct {
+}
+
+func (*UnimplementedAuthServiceServer) Auth(ctx context.Context, req *AuthReq) (*AuthResp, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method Auth not implemented")
+}
+
 func RegisterAuthServiceServer(s *grpc.Server, srv AuthServiceServer) {
 	s.RegisterService(&_AuthService_serviceDesc, srv)
 }
diff --git a/proto/auth/authdb_service.pb.go b/proto/auth/authdb_service.pb.go
index 1bfeb09..6cfc35b 100644
--- a/proto/auth/authdb_service.pb.go
+++ b/proto/auth/authdb_service.pb.go
@@ -8,6 +8,8 @@
 	fmt "fmt"
 	proto "github.com/golang/protobuf/proto"
 	grpc "google.golang.org/grpc"
+	codes "google.golang.org/grpc/codes"
+	status "google.golang.org/grpc/status"
 	math "math"
 )
 
@@ -73,6 +75,14 @@
 	CheckMembership(context.Context, *CheckMembershipReq) (*CheckMembershipResp, error)
 }
 
+// UnimplementedAuthDBServiceServer can be embedded to have forward compatible implementations.
+type UnimplementedAuthDBServiceServer struct {
+}
+
+func (*UnimplementedAuthDBServiceServer) CheckMembership(ctx context.Context, req *CheckMembershipReq) (*CheckMembershipResp, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method CheckMembership not implemented")
+}
+
 func RegisterAuthDBServiceServer(s *grpc.Server, srv AuthDBServiceServer) {
 	s.RegisterService(&_AuthDBService_serviceDesc, srv)
 }
diff --git a/proto/cache/cache_service.pb.go b/proto/cache/cache_service.pb.go
index 30a7f60..e856ad6 100644
--- a/proto/cache/cache_service.pb.go
+++ b/proto/cache/cache_service.pb.go
@@ -8,6 +8,8 @@
 	fmt "fmt"
 	proto "github.com/golang/protobuf/proto"
 	grpc "google.golang.org/grpc"
+	codes "google.golang.org/grpc/codes"
+	status "google.golang.org/grpc/status"
 	math "math"
 )
 
@@ -83,6 +85,17 @@
 	Put(context.Context, *PutReq) (*PutResp, error)
 }
 
+// UnimplementedCacheServiceServer can be embedded to have forward compatible implementations.
+type UnimplementedCacheServiceServer struct {
+}
+
+func (*UnimplementedCacheServiceServer) Get(ctx context.Context, req *GetReq) (*GetResp, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method Get not implemented")
+}
+func (*UnimplementedCacheServiceServer) Put(ctx context.Context, req *PutReq) (*PutResp, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method Put not implemented")
+}
+
 func RegisterCacheServiceServer(s *grpc.Server, srv CacheServiceServer) {
 	s.RegisterService(&_CacheService_serviceDesc, srv)
 }
diff --git a/proto/exec/exec_service.pb.go b/proto/exec/exec_service.pb.go
index bae446b..cfa34f3 100644
--- a/proto/exec/exec_service.pb.go
+++ b/proto/exec/exec_service.pb.go
@@ -9,6 +9,8 @@
 	proto "github.com/golang/protobuf/proto"
 	api "go.chromium.org/goma/server/proto/api"
 	grpc "google.golang.org/grpc"
+	codes "google.golang.org/grpc/codes"
+	status "google.golang.org/grpc/status"
 	math "math"
 )
 
@@ -144,6 +146,14 @@
 	Exec(context.Context, *api.ExecReq) (*api.ExecResp, error)
 }
 
+// UnimplementedExecServiceServer can be embedded to have forward compatible implementations.
+type UnimplementedExecServiceServer struct {
+}
+
+func (*UnimplementedExecServiceServer) Exec(ctx context.Context, req *api.ExecReq) (*api.ExecResp, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method Exec not implemented")
+}
+
 func RegisterExecServiceServer(s *grpc.Server, srv ExecServiceServer) {
 	s.RegisterService(&_ExecService_serviceDesc, srv)
 }
diff --git a/proto/execlog/log_service.pb.go b/proto/execlog/log_service.pb.go
index 2cd6d32..1384f2d 100644
--- a/proto/execlog/log_service.pb.go
+++ b/proto/execlog/log_service.pb.go
@@ -9,6 +9,8 @@
 	proto "github.com/golang/protobuf/proto"
 	api "go.chromium.org/goma/server/proto/api"
 	grpc "google.golang.org/grpc"
+	codes "google.golang.org/grpc/codes"
+	status "google.golang.org/grpc/status"
 	math "math"
 )
 
@@ -75,6 +77,14 @@
 	SaveLog(context.Context, *api.SaveLogReq) (*api.SaveLogResp, error)
 }
 
+// UnimplementedLogServiceServer can be embedded to have forward compatible implementations.
+type UnimplementedLogServiceServer struct {
+}
+
+func (*UnimplementedLogServiceServer) SaveLog(ctx context.Context, req *api.SaveLogReq) (*api.SaveLogResp, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method SaveLog not implemented")
+}
+
 func RegisterLogServiceServer(s *grpc.Server, srv LogServiceServer) {
 	s.RegisterService(&_LogService_serviceDesc, srv)
 }
diff --git a/proto/file/file_service.pb.go b/proto/file/file_service.pb.go
index 7d00e6a..990b420 100644
--- a/proto/file/file_service.pb.go
+++ b/proto/file/file_service.pb.go
@@ -9,6 +9,8 @@
 	proto "github.com/golang/protobuf/proto"
 	api "go.chromium.org/goma/server/proto/api"
 	grpc "google.golang.org/grpc"
+	codes "google.golang.org/grpc/codes"
+	status "google.golang.org/grpc/status"
 	math "math"
 )
 
@@ -87,6 +89,17 @@
 	LookupFile(context.Context, *api.LookupFileReq) (*api.LookupFileResp, error)
 }
 
+// UnimplementedFileServiceServer can be embedded to have forward compatible implementations.
+type UnimplementedFileServiceServer struct {
+}
+
+func (*UnimplementedFileServiceServer) StoreFile(ctx context.Context, req *api.StoreFileReq) (*api.StoreFileResp, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method StoreFile not implemented")
+}
+func (*UnimplementedFileServiceServer) LookupFile(ctx context.Context, req *api.LookupFileReq) (*api.LookupFileResp, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method LookupFile not implemented")
+}
+
 func RegisterFileServiceServer(s *grpc.Server, srv FileServiceServer) {
 	s.RegisterService(&_FileService_serviceDesc, srv)
 }
diff --git a/proto/settings/settings_service.pb.go b/proto/settings/settings_service.pb.go
index 77475e9..c1d5b39 100644
--- a/proto/settings/settings_service.pb.go
+++ b/proto/settings/settings_service.pb.go
@@ -8,6 +8,8 @@
 	fmt "fmt"
 	proto "github.com/golang/protobuf/proto"
 	grpc "google.golang.org/grpc"
+	codes "google.golang.org/grpc/codes"
+	status "google.golang.org/grpc/status"
 	math "math"
 )
 
@@ -72,6 +74,14 @@
 	Get(context.Context, *SettingsReq) (*SettingsResp, error)
 }
 
+// UnimplementedSettingsServiceServer can be embedded to have forward compatible implementations.
+type UnimplementedSettingsServiceServer struct {
+}
+
+func (*UnimplementedSettingsServiceServer) Get(ctx context.Context, req *SettingsReq) (*SettingsResp, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method Get not implemented")
+}
+
 func RegisterSettingsServiceServer(s *grpc.Server, srv SettingsServiceServer) {
 	s.RegisterService(&_SettingsService_serviceDesc, srv)
 }
diff --git a/remoteexec/adapter.go b/remoteexec/adapter.go
index 489b9ba..ec73df0 100644
--- a/remoteexec/adapter.go
+++ b/remoteexec/adapter.go
@@ -110,9 +110,10 @@
 	if err != nil {
 		return nil, err
 	}
+	// https://github.com/bazelbuild/remote-apis/blob/a5c577357528b33a4adff88c0c7911dd086c6923/build/bazel/remote/execution/v2/remote_execution.proto#L1460
 	// https://github.com/grpc/grpc-go/blob/master/Documentation/grpc-metadata.md#storing-binary-data-in-metadata
 	return metadata.AppendToOutgoingContext(ctx,
-		"google.devtools.remoteexecution.v1test.requestmetadata-bin",
+		"build.bazel.remote.execution.v2.requestmetadata-bin",
 		string(b)), nil
 }
 
diff --git a/remoteexec/adapter_test.go b/remoteexec/adapter_test.go
index 3f31caf..647e055 100644
--- a/remoteexec/adapter_test.go
+++ b/remoteexec/adapter_test.go
@@ -936,7 +936,7 @@
 
 	// files and executables might contain extra "out/Release/run.sh".
 	wantFiles := []string{"out/Release/run.sh", "bin/clang", "include/hello.h", "src/hello.c"}
-	wantExecutables := []string{"bin/clang"}
+	wantExecutables := []string{"bin/clang", "out/Release/run.sh"}
 
 	for _, f := range wantFiles {
 		if !files[f].isFile {
@@ -1049,10 +1049,6 @@
 			Value: "/b/c/w",
 		},
 		{
-			Name:  "PWD",
-			Value: "/b/c/w/out/Debug",
-		},
-		{
 			Name:  "WORK_DIR",
 			Value: "out/Debug",
 		},
@@ -1071,8 +1067,8 @@
 	}
 
 	// files and executables might contain extra "out/Release/run.sh".
-	wantFiles := []string{"out/Debug/run.sh", "bin/clang", "include/hello.h", "src/hello.c"}
-	wantExecutables := []string{"bin/clang"}
+	wantFiles := []string{"out/Debug/run.sh", "out/Debug/env_file_for_docker", "bin/clang", "include/hello.h", "src/hello.c"}
+	wantExecutables := []string{"bin/clang", "out/Debug/run.sh"}
 
 	for _, f := range wantFiles {
 		if !files[f].isFile {
@@ -1088,6 +1084,9 @@
 	if got, want := files["out/Debug/run.sh"].digest, digest.Bytes("wrapper-script", []byte(wrapperScript)).Digest(); !proto.Equal(got, want) {
 		t.Errorf("digest of out/Debug/run.sh: %s != %s", got, want)
 	}
+	if got, want := files["out/Debug/env_file_for_docker"].digest, digest.Bytes("envfile", []byte("PWD=/b/c/w/out/Debug")).Digest(); !proto.Equal(got, want) {
+		t.Errorf("digest of out/Debug/env_file_for_docker: %s != %s", got, want)
+	}
 }
 
 func TestAdapterDockerProperties(t *testing.T) {
diff --git a/remoteexec/client.go b/remoteexec/client.go
index e691349..3e3f991 100644
--- a/remoteexec/client.go
+++ b/remoteexec/client.go
@@ -226,35 +226,46 @@
 				logger.Errorf("%s", err)
 				return err
 			}
-			return erespErr(resp)
+			return erespErr(ctx, resp)
 		}
 	})
 	recordFinish()
+	if err == nil {
+		err = status.FromProto(resp.GetStatus()).Err()
+	}
 	return opName, resp, err
 }
 
 // erespErr returns codes.Unavailable if it has retriable failure result.
 // returns nil otherwise (to terminates retrying, even if eresp contains
 // error status).
-func erespErr(eresp *rpb.ExecuteResponse) error {
+func erespErr(ctx context.Context, eresp *rpb.ExecuteResponse) error {
+	logger := log.FromContext(ctx)
 	st := eresp.GetStatus()
-	// see bazel remote retrier.
-	// https://github.com/bazelbuild/bazel/blob/master/src/main/java/com/google/devtools/build/lib/remote/RemoteRetrier.java
-	// also see https://cloud.google.com/pubsub/docs/reference/error-codes
+	// https://github.com/bazelbuild/remote-apis/blob/e7282cf0f0e16e7ba84209be5417279e6815bee7/build/bazel/remote/execution/v2/remote_execution.proto#L83
+	// FAILED_PRECONDITION:
+	//   one or more errors occured in setting up the action
+	//   requested, such as a missing input or command or
+	//   no worker being available. The client may be able to
+	//   fix the errors and retry.
+	// UNAVAILABLE:
+	//   Due to transient condition, such as all workers being
+	//   occupied (and the server does not support a queue), the
+	//   action could not be started. The client should retry.
 	//
-	// we don't retry codes.Unauthenticated; end user's access token would
-	// be expired, so retry also get unauthenticated (we don't have
-	// refresh token).
-	//
-	// codes.DeadlineExceeded might fail soon with codes.DeadlineExceeded,
-	// but if server side sets shorter deadline and caller has more time
-	// in context, retry would succeed.
+	// Other error would be non retriable.
 	switch codes.Code(st.GetCode()) {
-	case codes.Unknown, codes.DeadlineExceeded, codes.Aborted, codes.Internal, codes.Unavailable, codes.ResourceExhausted:
+	case codes.OK:
+	case codes.FailedPrecondition:
+		logger.Warnf("execute response: failed precondition: %s", st)
+		fallthrough
+	case codes.Unavailable:
 		st = proto.Clone(st).(*spb.Status)
 		// codes.Unavailable, so that rpc.Retry will retry.
 		st.Code = int32(codes.Unavailable)
 		return status.FromProto(st).Err()
+	default:
+		logger.Errorf("execute response: error %s", st)
 	}
 	return nil
 }
diff --git a/remoteexec/exec.go b/remoteexec/exec.go
index 0fc0164..f88ca88 100644
--- a/remoteexec/exec.go
+++ b/remoteexec/exec.go
@@ -202,15 +202,12 @@
 }
 
 func (r *request) addPlatformProperty(ctx context.Context, name, value string) {
-	logger := log.FromContext(ctx)
 	for _, p := range r.platform.Properties {
 		if p.Name == name {
-			logger.Infof("platform property update %s: %s->%s", name, p.Value, value)
 			p.Value = value
 			return
 		}
 	}
-	logger.Infof("platform property add %s: %s", name, value)
 	r.platform.Properties = append(r.platform.Properties, &rpb.Platform_Property{
 		Name:  name,
 		Value: value,
@@ -579,13 +576,13 @@
   exit 1
 fi
 
-# PWD might be /proc/self/cwd, but we need actual path for
-# INPUT_ROOT_DIR and WORK_DIR, so we can't use PWD for --workdir.
+# TODO: use PWD instead of INPUT_ROOT_DIR if appricable.
 
 docker run \
  -u "$(id -u)" \
  --volume "${rundir}:${INPUT_ROOT_DIR}" \
  --workdir "${INPUT_ROOT_DIR}/${WORK_DIR}" \
+ --env-file "${WORK_DIR}/env_file_for_docker" \
  --rm \
  "$image" \
  "$@"
@@ -627,21 +624,22 @@
 	var files []fileDesc
 
 	args := buildArgs(ctx, cmdConfig, argv0, r.gomaReq)
+	// TODO: only allow whitelisted envs.
 
 	pathType := cmdConfig.GetCmdDescriptor().GetSetup().GetPathType()
+	const posixWrapperName = "run.sh"
 	switch pathType {
 	case cmdpb.CmdDescriptor_POSIX:
 		if r.needChroot {
 			logger.Infof("run with chroot")
-			envs = append(envs, r.gomaReq.Env...)
 			// needed for bind mount.
 			r.addPlatformProperty(ctx, "dockerPrivileged", "true")
 			// needed for chroot command and mount command.
 			r.addPlatformProperty(ctx, "dockerRunAsRoot", "true")
-			nsjailCfg := nsjailConfig(cwd)
+			nsjailCfg := nsjailConfig(cwd, r.filepath, r.gomaReq.GetToolchainSpecs(), r.gomaReq.Env)
 			files = []fileDesc{
 				{
-					name:         "run.sh",
+					name:         posixWrapperName,
 					data:         digest.Bytes("nsjail-run-wrapper-script", []byte(nsjailRunWrapperScript)),
 					isExecutable: true,
 				},
@@ -651,17 +649,25 @@
 				},
 			}
 		} else {
-			var d digest.Data
 			err = cwdAgnosticReq(ctx, cmdConfig, r.filepath, r.gomaReq.Arg, r.gomaReq.Env)
 			if err != nil {
 				logger.Infof("non cwd agnostic: %v", err)
-				d = digest.Bytes("wrapper-script", []byte(wrapperScript))
 				envs = append(envs, fmt.Sprintf("INPUT_ROOT_DIR=%s", r.tree.RootDir()))
-				envs = append(envs, r.gomaReq.Env...)
+
 				r.addPlatformProperty(ctx, "dockerSiblingContainers", "true")
+				files = []fileDesc{
+					{
+						name:         posixWrapperName,
+						data:         digest.Bytes("wrapper-script", []byte(wrapperScript)),
+						isExecutable: true,
+					},
+					{
+						name: "env_file_for_docker",
+						data: digest.Bytes("envfile", []byte(strings.Join(r.gomaReq.Env, "\n"))),
+					},
+				}
 			} else {
 				logger.Infof("cwd agnostic")
-				d = digest.Bytes("cwd-agnostic-wrapper-script", []byte(cwdAgnosticWrapperScript))
 				for _, e := range r.gomaReq.Env {
 					if strings.HasPrefix(e, "PWD=") {
 						// PWD is usually absolute path.
@@ -671,13 +677,13 @@
 					}
 					envs = append(envs, e)
 				}
-			}
-			files = []fileDesc{
-				{
-					name:         "run.sh",
-					data:         d,
-					isExecutable: true,
-				},
+				files = []fileDesc{
+					{
+						name:         posixWrapperName,
+						data:         digest.Bytes("cwd-agnostic-wrapper-script", []byte(cwdAgnosticWrapperScript)),
+						isExecutable: true,
+					},
+				}
 			}
 		}
 	case cmdpb.CmdDescriptor_WINDOWS:
@@ -721,14 +727,9 @@
 
 	// if a wrapper exists in cwd, `wrapper` does not have a directory name.
 	// It cannot be callable on POSIX because POSIX do not contain "." in
-	// its PATH.  Although "." is included in PATH on Windows, there can be
-	// a risk unexpected scripts with the same name would be called.
-	// Let's avoid the situation with explicitly specifying the directory
-	// name i.e. ".".
-	if r.filepath.Base(wrapperPath) == wrapperPath {
-		// Since Join omit "." and this is only the case Join must not
-		// omit ".", we need to join by ourselves here.
-		wrapperPath = "." + r.filepath.PathSep() + wrapperPath
+	// its PATH.
+	if wrapperPath == posixWrapperName {
+		wrapperPath = "./" + posixWrapperName
 	}
 	r.args = append([]string{wrapperPath}, args...)
 	return nil
@@ -831,7 +832,7 @@
 		return
 	}
 	logger := log.FromContext(ctx)
-	logger.Infof("command digest: %v %s", data.Digest(), command)
+	logger.Infof("command digest: %v", data.Digest())
 
 	r.digestStore.Set(data)
 	r.action.CommandDigest = data.Digest()
diff --git a/remoteexec/nsjail.go b/remoteexec/nsjail.go
index 455c97e..c86532c 100644
--- a/remoteexec/nsjail.go
+++ b/remoteexec/nsjail.go
@@ -5,8 +5,12 @@
 package remoteexec
 
 import (
+	"sort"
+	"strings"
+
 	"github.com/golang/protobuf/proto"
 
+	gomapb "go.chromium.org/goma/server/proto/api"
 	nsjailpb "go.chromium.org/goma/server/proto/nsjail"
 )
 
@@ -48,8 +52,9 @@
   # directory will be mounted by nsjail later.
   mkdir -p "$chroot_workdir/$d"
 done
-# needed to make nsjail bind /dev/urandom.
+# needed to make nsjail bind device files.
 touch "$chroot_workdir/dev/urandom"
+touch "$chroot_workdir/dev/null"
 
 # currently running with root. run the command with nobody:nogroup with chroot.
 # We use nsjail to chdir without running bash script inside chroot, and
@@ -58,10 +63,33 @@
 `
 )
 
+// pathFromToolchainSpec returns ':'-joined directories of paths in toolchain spec.
+// Since symlinks may point to executables, having directories with executables
+// may not work, but it is a bit cumbersome to analyze symlinks.
+// Also, having library directories in PATH should be harmless because
+// the Goma client may not include multiple subprograms with the same name.
+func pathFromToolchainSpec(cfp clientFilePath, ts []*gomapb.ToolchainSpec) string {
+	m := make(map[string]bool)
+	for _, e := range ts {
+		m[cfp.Dir(e.GetPath())] = true
+	}
+	var r []string
+	for k := range m {
+		if k == "" || k == "." {
+			continue
+		}
+		r = append(r, k)
+	}
+	// This function must return the same result for the same input, but go
+	// does not guarantee the iteration order.
+	sort.Strings(r)
+	return strings.Join(r, ":")
+}
+
 // nsjailConfig returns nsjail configuration.
 // When you modify followings, please make sure it matches
 // nsjailRunWrapperScript above.
-func nsjailConfig(cwd string) []byte {
+func nsjailConfig(cwd string, cfp clientFilePath, ts []*gomapb.ToolchainSpec, envs []string) []byte {
 	chrootWorkdir := "/tmp/goma_chroot"
 	cfg := &nsjailpb.NsJailConfig{
 		Uidmap: []*nsjailpb.IdMap{
@@ -85,6 +113,12 @@
 				IsDir:  proto.Bool(true),
 			},
 			{
+				Src:    proto.String("/dev/null"),
+				Dst:    proto.String("/dev/null"),
+				Rw:     proto.Bool(true),
+				IsBind: proto.Bool(true),
+			},
+			{
 				Src:    proto.String("/dev/urandom"),
 				Dst:    proto.String("/dev/urandom"),
 				IsBind: proto.Bool(true),
@@ -94,15 +128,16 @@
 		// TODO: use log file and print to server log.
 		LogLevel:  nsjailpb.LogLevel_WARNING.Enum(),
 		MountProc: proto.Bool(true),
-		Envar: []string{
-			// HACK: ChromeOS clang wrapper needs this.
-			// https://chromium.googlesource.com/chromiumos/overlays/chromiumos-overlay/+/f15eab2d792acfe1ee5eca4d95792c081558cf84/sys-devel/gcc/files/sysroot_wrapper.body#69
-			// TODO: set better path upon needs.
-			// e.g. auto generate from where executables and symlink
-			// to executable exists, or choose directories with
-			// executables from PATH sent by clients.
-			"PATH=",
-		},
+		Envar: append(
+			[]string{
+				"PATH=" + pathFromToolchainSpec(cfp, ts),
+				// Dummy home directory is needed by pnacl-clang to
+				// import site.py to import user-defined python
+				// packages.
+				"HOME=/",
+			},
+			// Add client-side environemnt to execution environment.
+			envs...),
 		RlimitAsType:    nsjailpb.RLimit_INF.Enum(),
 		RlimitFsizeType: nsjailpb.RLimit_INF.Enum(),
 		// TODO: relax RLimit from the default.
diff --git a/remoteexec/nsjail_test.go b/remoteexec/nsjail_test.go
new file mode 100644
index 0000000..f9bc831
--- /dev/null
+++ b/remoteexec/nsjail_test.go
@@ -0,0 +1,133 @@
+// Copyright 2019 The Goma 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 remoteexec
+
+import (
+	"testing"
+
+	"github.com/golang/protobuf/proto"
+
+	"go.chromium.org/goma/server/command/descriptor/posixpath"
+	gomapb "go.chromium.org/goma/server/proto/api"
+)
+
+func TestPathFromToolchainSpec(t *testing.T) {
+	for _, tc := range []struct {
+		desc string
+		cfp  clientFilePath
+		ts   []*gomapb.ToolchainSpec
+		want string
+	}{
+		{
+			desc: "empty",
+			cfp:  posixpath.FilePath{},
+			ts:   nil,
+			want: "",
+		},
+		{
+			desc: "one toolchain spec",
+			cfp:  posixpath.FilePath{},
+			ts: []*gomapb.ToolchainSpec{
+				{
+					Path:         proto.String("/usr/bin/clang"),
+					Hash:         proto.String("fe4c1bb3b68376901c9f9e87dc1196a81f598eb854061ddfc5f85ef7e054feed"),
+					Size:         proto.Int64(86003704),
+					IsExecutable: proto.Bool(true),
+				},
+			},
+			want: "/usr/bin",
+		},
+		{
+			desc: "toolchain spec in the same directory",
+			cfp:  posixpath.FilePath{},
+			ts: []*gomapb.ToolchainSpec{
+				{
+					Path:         proto.String("/usr/bin/clang"),
+					Hash:         proto.String("fe4c1bb3b68376901c9f9e87dc1196a81f598eb854061ddfc5f85ef7e054feed"),
+					Size:         proto.Int64(86003704),
+					IsExecutable: proto.Bool(true),
+				},
+				{
+					Path:        proto.String("/usr/bin/clang++"),
+					SymlinkPath: proto.String("clang"),
+				},
+			},
+			want: "/usr/bin",
+		},
+		{
+			desc: "toolchain spec in the different directory",
+			cfp:  posixpath.FilePath{},
+			ts: []*gomapb.ToolchainSpec{
+				{
+					Path:         proto.String("/usr/bin/clang"),
+					Hash:         proto.String("fe4c1bb3b68376901c9f9e87dc1196a81f598eb854061ddfc5f85ef7e054feed"),
+					Size:         proto.Int64(86003704),
+					IsExecutable: proto.Bool(true),
+				},
+				{
+					Path:         proto.String("/bin/bash"),
+					Hash:         proto.String("d80e7ffe8836eb30bafb138def4c1a6e3f586d98ec39a26d9549a92141e95281"),
+					Size:         proto.Int64(715328),
+					IsExecutable: proto.Bool(true),
+				},
+			},
+			want: "/bin:/usr/bin",
+		},
+		{
+			desc: "complexed",
+			cfp:  posixpath.FilePath{},
+			ts: []*gomapb.ToolchainSpec{
+				{
+					Path:         proto.String("../../native_client/toolchain/linux_x86/pnacl_newlib/bin/pnacl-clang++"),
+					Hash:         proto.String("dummy"),
+					Size:         proto.Int64(0),
+					IsExecutable: proto.Bool(true),
+				},
+				{
+					Path:         proto.String("/home/goma/chrome_root/src/native_client/toolchain/linux_x86/pnacl_newlib/bin/clang"),
+					Hash:         proto.String("dummy"),
+					Size:         proto.Int64(0),
+					IsExecutable: proto.Bool(true),
+				},
+				{
+					// lib directory is also included.
+					Path: proto.String("/usr/lib64/python2.7/abc.py"),
+					Hash: proto.String("dummy"),
+					Size: proto.Int64(0),
+				},
+				{
+					Path:        proto.String("/usr/bin/python"),
+					SymlinkPath: proto.String("python-wrapper"),
+				},
+				{
+					Path:         proto.String("/usr/bin/python-wrapper"),
+					Hash:         proto.String("dummy"),
+					Size:         proto.Int64(0),
+					IsExecutable: proto.Bool(true),
+				},
+				{
+					Path:         proto.String("/usr/bin/bash"),
+					Hash:         proto.String("dummy"),
+					Size:         proto.Int64(0),
+					IsExecutable: proto.Bool(true),
+				},
+				{
+					Path:         proto.String("/lib64/libc-2.27.so"),
+					Hash:         proto.String("dummy"),
+					Size:         proto.Int64(0),
+					IsExecutable: proto.Bool(true),
+				},
+			},
+			want: "../../native_client/toolchain/linux_x86/pnacl_newlib/bin:/home/goma/chrome_root/src/native_client/toolchain/linux_x86/pnacl_newlib/bin:/lib64:/usr/bin:/usr/lib64/python2.7",
+		},
+	} {
+		t.Run(tc.desc, func(t *testing.T) {
+			actual := pathFromToolchainSpec(tc.cfp, tc.ts)
+			if actual != tc.want {
+				t.Errorf("pathFromToolchainSpec(%v, %v) = %q; want %q", tc.cfp, tc.ts, actual, tc.want)
+			}
+		})
+	}
+}