After UserCacheDir and UserHomeDir, the only remaining piece which is
commonly needed and portable is a per-user directory to store persistent
files.
For that purpose, UserCacheDir is wrong, as it's meant only for
temporary files. UserHomeDir is also far from ideal, as that clutters
the user's home directory.
Add UserConfigDir, which is implemented in a similar manner to
UserConfigDir.
Fixes#29960.
Change-Id: I7d7a56615103cf76e2b5e2bab2029a6b09d19f0b
Reviewed-on: https://go-review.googlesource.com/c/go/+/160877
Run-TryBot: Daniel Martí <mvdan@mvdan.cc>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
When Stdin, Stdout, and Stderr are nil, there are no goroutines to keep
track of, so we don't need a channel.
And in startProcess, preallocate the right size for sysattr.Files,
saving a bit of space and a couple of slice growth allocs.
name old time/op new time/op delta
ExecHostname-8 419µs ± 0% 417µs ± 1% ~ (p=0.093 n=6+6)
name old alloc/op new alloc/op delta
ExecHostname-8 6.40kB ± 0% 6.28kB ± 0% -1.86% (p=0.002 n=6+6)
name old allocs/op new allocs/op delta
ExecHostname-8 34.0 ± 0% 31.0 ± 0% -8.82% (p=0.002 n=6+6)
Change-Id: Ic1d617f29e9c6431cdcadc7f9bb992750a6d5f48
Reviewed-on: https://go-review.googlesource.com/c/164801
Run-TryBot: Daniel Martí <mvdan@mvdan.cc>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
Most notably, it's missing on Windows machines. For example,
windows-amd64-race started failing consistently:
--- FAIL: BenchmarkExecEcho
bench_test.go:15: could not find echo: exec: "echo": executable file not found in %PATH%
We can also reproduce this from Linux with Wine:
$ GOOS=windows go test -bench=. -benchtime=1x -run=- -exec wine
--- FAIL: BenchmarkExecEcho
bench_test.go:15: could not find echo: exec: "echo": executable file not found in %PATH%
Instead, use the "hostname" program, which is available on Windows too.
Interestingly enough, it's also slightly faster than "echo". Any program
is fine as long as it's close enough to a no-op, though.
name old time/op new time/op delta
ExecEcho-8 422µs ± 0% 395µs ± 0% -6.39% (p=0.004 n=6+5)
name old alloc/op new alloc/op delta
ExecEcho-8 6.39kB ± 0% 6.42kB ± 0% +0.53% (p=0.002 n=6+6)
name old allocs/op new allocs/op delta
ExecEcho-8 36.0 ± 0% 36.0 ± 0% ~ (all equal)
Change-Id: I772864d69979172b5cf807552c84d0e165e73051
Reviewed-on: https://go-review.googlesource.com/c/164704
Run-TryBot: Daniel Martí <mvdan@mvdan.cc>
Reviewed-by: Josh Bleecher Snyder <josharian@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
We're always going to add stdin, stdout, and stderr to childFiles, so
its length will be at least three. The final length will be those three
elements plus however many files were given via ExtraFiles.
Allocate for that final length directly, saving two slice growth allocs
in the common case where ExtraFiles is empty.
name old time/op new time/op delta
ExecEcho-8 435µs ± 0% 435µs ± 0% ~ (p=0.394 n=6+6)
name old alloc/op new alloc/op delta
ExecEcho-8 6.39kB ± 0% 6.37kB ± 0% -0.39% (p=0.002 n=6+6)
name old allocs/op new allocs/op delta
ExecEcho-8 36.0 ± 0% 34.0 ± 0% -5.56% (p=0.002 n=6+6)
Change-Id: Ib702c0da1e43f0a55ed937af6d45fca6a170e8f3
Reviewed-on: https://go-review.googlesource.com/c/164898
Run-TryBot: Daniel Martí <mvdan@mvdan.cc>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
The common case is that most env vars are distinct;
optimize for that.
name old time/op new time/op delta
ExecEcho-8 2.16ms ± 3% 2.14ms ± 1% ~ (p=0.315 n=10+10)
name old alloc/op new alloc/op delta
ExecEcho-8 7.87kB ± 0% 6.35kB ± 0% -19.31% (p=0.000 n=9+10)
name old allocs/op new allocs/op delta
ExecEcho-8 72.0 ± 0% 69.0 ± 0% -4.17% (p=0.000 n=10+10)
Change-Id: I42bb696c6862f2ea12c5cbd2f24c64336a7a759a
Reviewed-on: https://go-review.googlesource.com/c/164960
Run-TryBot: Josh Bleecher Snyder <josharian@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
windows-arm TMP directory live inside such link (see
https://github.com/golang/go/issues/29746#issuecomment-456526811 for
details), so symlinks like that will be common at least on windows-arm.
This CL builds on current syscall.Readlink implementation. Main
difference between the two is how new code handles symlink targets,
like \??\Volume{ABCD}\.
New implementation uses Windows CreateFile API with
FILE_FLAG_OPEN_REPARSE_POINT flag to get \??\Volume{ABCD}\ file handle.
And then it uses Windows GetFinalPathNameByHandle with VOLUME_NAME_DOS
flag to convert that handle into standard Windows path.
FILE_FLAG_OPEN_REPARSE_POINT flag ensures that symlink is not followed
when CreateFile opens the file.
Fixes#30463
Change-Id: I33b18227ce36144caed694169ef2e429fd995fb4
Reviewed-on: https://go-review.googlesource.com/c/164201
Run-TryBot: Alex Brainman <alex.brainman@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
- add EINTR loop on Darwin
- return PathError on error
- call newFile rather than NewFile
This tries to minimize the possibility of any future changes.
It would be nice to put openFdAt in the same file as openFileNolog,
but build tags forbid.
Updates #29983
Change-Id: I866002416d6473fbfd80ff6ef09b2bc4607f2934
Reviewed-on: https://go-review.googlesource.com/c/160181
Run-TryBot: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Damien Neil <dneil@google.com>
The kqueue based netpoller always registers file descriptors with EVFILT_READ and EVFILT_WRITE.
However only EVFILT_READ notification is supported for regular files.
On FreeBSD a regular file is always reported as ready for writing, resulting in a busy wait.
On Darwin, Dragonfly, NetBSD and OpenBSD, a regular file is reported as ready for both reading and writing only once.
Updates #19093
Change-Id: If284341f60c6c2332fb5499637d4cfa7a4e26b7b
Reviewed-on: https://go-review.googlesource.com/c/156379
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
CL 138595 introduced the new names when the hardcoded stat8 definitions was replaced
with a cgo generated one.
Fixes#29393
Updates #22448
Change-Id: I6309958306329ff301c17344b2e0ead0cc874224
Reviewed-on: https://go-review.googlesource.com/c/155958
Run-TryBot: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
I was confused by the juxtaposition of os.Interrupt docs, which are
"guaranteed to exist on all platforms" in one sentence and then
"not implemented" in the next sentence. Reading the code reveals
"not implemented" refers specifically to the implementation of
os.Process.Signal on Windows, not to the os.Interrupt variable itself.
Reword the doc to make this distinction clearer.
Fixes#27854.
Change-Id: I5fe7cddea61fa1954cef2006dc51b8fa8ece4d6e
Reviewed-on: https://go-review.googlesource.com/c/137336
Reviewed-by: Alex Brainman <alex.brainman@gmail.com>
If TMP environment variable is set to Z:\, TempDir returns Z:.
But Z: refers to current directory on Z:, while Z:\ refers to root
directory on Z:. Adjust TempDir to return Z:\.
Fixes#29291
Change-Id: If04d0c7977a8ac2d9d558307502e81beb68776ef
Reviewed-on: https://go-review.googlesource.com/c/154384
Run-TryBot: Alex Brainman <alex.brainman@gmail.com>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
Permission bits are most commonly viewed in string form (rwx-- etc) or
in octal form (0755), but the latter is relatively rare in Go.
Demonstrate how to print a FileMode in readable octal format.
Change-Id: I41feb801bcecb5077d4eabafdea27c149fc179a1
Reviewed-on: https://go-review.googlesource.com/c/154423
Run-TryBot: Ian Lance Taylor <iant@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
It appears calling GetFileInformationByHandleEx with
FILE_ATTRIBUTE_TAG_INFO fails on FAT file system. FAT does not
support symlinks, so assume there are no symlnks when
GetFileInformationByHandleEx returns ERROR_INVALID_PARAMETER.
Fixes#29214
Change-Id: If2d9f3288bd99637681ab5fd4e4581c77b578a69
Reviewed-on: https://go-review.googlesource.com/c/154377
Run-TryBot: Alex Brainman <alex.brainman@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
The getdirentries syscall is considered private API on iOS and is
rejected by the App Store submission checks. Replace it with the
fdopendir/readdir_r/closedir syscalls.
Fixes#28984
Change-Id: I73341b124310e9cb34834a95f946769f337ec5b7
Reviewed-on: https://go-review.googlesource.com/c/153338
Reviewed-by: Keith Randall <khr@golang.org>
This increases the time to wait from 1 to 2 seconds in the
TestAtomicStop testcase. When running with gccgo on ppc64
& ppc64le on a loaded systems these testcases can
intermittently fail with the current value.
Updates #29046
Change-Id: If420274dd65926d933a3024903b5c757c300bd60
Reviewed-on: https://go-review.googlesource.com/c/153826
Run-TryBot: Lynn Boger <laboger@linux.vnet.ibm.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
Otherwise we can fail to remove a unreadable empty directory.
Fixes#29178
Change-Id: I43d5c89fce57a86626abe2a1c2bbf145716e087b
Reviewed-on: https://go-review.googlesource.com/c/153720
Run-TryBot: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
The original value of 65537 consistently caused the test to fail on
Solaris. The new value of 131073 consistently lets the test pass.
Change-Id: If1a76ab89aa8f661ea049113addd04b23a116534
Reviewed-on: https://go-review.googlesource.com/c/152164
Run-TryBot: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
UserHomeDir used to return an empty string if the corresponding
environment variable was not set. Changed it to return an error if the
variable is not set, to have the same signature and behaviour as UserCacheDir.
Fixes#28562
Change-Id: I42c497e8011ecfbbadebe7de1751575273be221c
Reviewed-on: https://go-review.googlesource.com/c/150418
Run-TryBot: Tobias Klauser <tobias.klauser@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
CL 150497 enabled TestRemoveAllDot on "noat" systems.
However, this test is failing on Plan 9 because the rmdir
system call allows to remove "." on Plan 9.
This change prevents the "noat" implementation of RemoveAll to
remove ".", so it remains consistent with the "at" implementation.
Fixes#28903.
Change-Id: Ifc8fe36bdd8053a4e416f0590663c844c97ce72a
Reviewed-on: https://go-review.googlesource.com/c/150621
Run-TryBot: David du Colombier <0intro@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
Return a PathError instead of an unwrapped syscall.EINVAL if the path
ends with dots.
As suggested by Roger Peppe in CL 150158.
Change-Id: I4d82a6ff64a979b67a843a1cc4fea58ed9326aed
Reviewed-on: https://go-review.googlesource.com/c/150160
Run-TryBot: Tobias Klauser <tobias.klauser@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
Prohibiting RemoveAll with paths that end in ".." was added with
CL 137442 in this release cycle, but it worked before and it should
continue to work.
Also run TestRemoveAllDot on all systems; the test is not specific to
the use of unlinkat and friends.
Change-Id: I277784c8915cd748fec318d2936062440d5d1fde
Reviewed-on: https://go-review.googlesource.com/c/150497
Reviewed-by: Tobias Klauser <tobias.klauser@gmail.com>
CL 146020 changed the behavior of RemoveAll("") on unix systems using
the *at functions to return syscall.EINVAL instead of nil. Adjust the
*at implementation to retain this behavior as is the case on the *noat
systems.
Additionally, also make sure RemoveAll("") on systems not using the "at
functions (e.g. nacl and js/wasm) follow the same behavior (which wasn't
the case previously).
Fixes#28830
Change-Id: I8383c1423fefe871d18ff49134a1d23077ec6867
Reviewed-on: https://go-review.googlesource.com/c/150158
Run-TryBot: Tobias Klauser <tobias.klauser@gmail.com>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
Reviewed-by: roger peppe <rogpeppe@gmail.com>
Go documentation style for boolean funcs is to say:
// Foo reports whether ...
func Foo() bool
(rather than "returns true if")
This CL also replaces 4 uses of "iff" with the same "reports whether"
wording, which doesn't lose any meaning, and will prevent people from
sending typo fixes when they don't realize it's "if and only if". In
the past I think we've had the typo CLs updated to just say "reports
whether". So do them all at once.
(Inspired by the addition of another "returns true if" in CL 146938
in fd_plan9.go)
Created with:
$ perl -i -npe 's/returns true if/reports whether/' $(git grep -l "returns true iff" | grep -v vendor)
$ perl -i -npe 's/returns true if/reports whether/' $(git grep -l "returns true if" | grep -v vendor)
Change-Id: Ided502237f5ab0d25cb625dbab12529c361a8b9f
Reviewed-on: https://go-review.googlesource.com/c/147037
Reviewed-by: Ian Lance Taylor <iant@golang.org>
This commit changes poll.PollDescriptor by poll.IsPollDescriptor. This
is needed for OS like AIX which have more than one FD using inside their
netpoll implementation.
Change-Id: I49e12a8d74045c501e19fdd8527cf166a3c64850
Reviewed-on: https://go-review.googlesource.com/c/146938
Run-TryBot: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
Follow CL 146020 and enable RemoveAll based on Unlinkat and Openat on
aix.
Updates #27029
Change-Id: I78b34ed671166ee6fa651d5f2025b88548ee6c68
Reviewed-on: https://go-review.googlesource.com/c/146937
Run-TryBot: Tobias Klauser <tobias.klauser@gmail.com>
Reviewed-by: Clément Chigot <clement.chigot@atos.net>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
Stat uses Windows FindFirstFile + CreateFile to gather symlink
information - FindFirstFile determines if file is a symlink,
and then CreateFile follows symlink to capture target details.
Lstat only uses FindFirstFile.
This CL replaces current approach with just a call to CreateFile.
Lstat uses FILE_FLAG_OPEN_REPARSE_POINT flag, that instructs
CreateFile not to follow symlink. Other than that both Stat and
Lstat look the same now. New code is simpler.
CreateFile + GetFileInformationByHandle (unlike FindFirstFile)
does not report reparse tag of a file. I tried to ignore reparse
tag altogether. And it works for symlinks and mount points.
Unfortunately (see https://github.com/moby/moby/issues/37026),
files on deduped disk volumes are reported with
FILE_ATTRIBUTE_REPARSE_POINT attribute set and reparse tag set
to IO_REPARSE_TAG_DEDUP. So, if we ignore reparse tag, Lstat
interprets deduped volume files as symlinks. That is incorrect.
So I had to add GetFileInformationByHandleEx call to gather
reparse tag after calling CreateFile and GetFileInformationByHandle.
Fixes#27225Fixes#27515
Change-Id: If60233bcf18836c147597cc17450d82f3f88c623
Reviewed-on: https://go-review.googlesource.com/c/143578
Run-TryBot: Alex Brainman <alex.brainman@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
Reviewed-by: Kirill Kolyshkin <kolyshkin@gmail.com>
Follow CL 146020 and enable RemoveAll based on Unlinkat and Openat on
freebsd.
Since the layout of syscall.Stat_t changes in FreeBSD 12, Fstatat needs
a compatibility wrapper akin to Fstatat in x/sys/unix. See CL 138595 and
CL 136816 for details.
Updates #27029
Change-Id: I8851a5b7fa658eaa6e69a1693150b16d9a68f36a
Reviewed-on: https://go-review.googlesource.com/c/146597
Run-TryBot: Tobias Klauser <tobias.klauser@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Yuval Pavel Zholkover <paulzhol@gmail.com>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
Also, use a random temporary directory rather than os.TempDir. Defer
removal of existing random temporary directories.
Change-Id: Id7549031cdf78a2bab28c07b6eeff621bdf6e49c
Reviewed-on: https://go-review.googlesource.com/c/146457
Run-TryBot: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Tobias Klauser <tobias.klauser@gmail.com>
On unix systems, long enough path names will fail when performing syscalls
like `Lstat`. The current RemoveAll uses several of these syscalls, and so
will fail for long paths. This can be risky, as it can let users "hide"
files from the system or otherwise make long enough paths for programs
to fail. By using `Unlinkat` and `Openat` syscalls instead, RemoveAll is
safer on unix systems. Initially implemented for linux, darwin, dragonfly,
netbsd and openbsd. Not yet implemented on freebsd due to fstatat 64-bit
inode compatibility issues.
Fixes#27029
Co-authored-by: Giuseppe Capizzi <gcapizzi@pivotal.io>
Co-authored-by: Julia Nedialkova <yulia.nedyalkova@sap.com>
Change-Id: I978a6a4986878fe076d3c7af86e7927675624a96
GitHub-Last-Rev: 9235489c81
GitHub-Pull-Request: golang/go#28494
Reviewed-on: https://go-review.googlesource.com/c/146020
Run-TryBot: Tobias Klauser <tobias.klauser@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Tobias Klauser <tobias.klauser@gmail.com>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
Windows implementation of Symlink uses CreateSymbolicLink Windows
API. The API requires to identify the target type: file or
directory. Current Symlink implementation uses Lstat to determine
symlink type, but Lstat will not be able to determine correct
result if destination is symlink. Replace Lstat call with Stat.
Fixes#28432
Change-Id: Ibee6d8ac21e2246bf8d0a019c4c66d38b09887d4
Reviewed-on: https://go-review.googlesource.com/c/145217
Run-TryBot: Alex Brainman <alex.brainman@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
This reverts commit 85143d3554.
Reason for revert: Breaking all Darwin and FreeBSD builds. Trybots did not pass for this.
Change-Id: I5494e14ad5ab9cf6e1e225a25b2e8b38f3359d13
Reviewed-on: https://go-review.googlesource.com/c/145897
Reviewed-by: Katie Hockman <katie@golang.org>
Run-TryBot: Katie Hockman <katie@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
On unix systems, long enough path names will fail when performing syscalls
like `Lstat`. The current RemoveAll uses several of these syscalls, and so
will fail for long paths. This can be risky, as it can let users "hide"
files from the system or otherwise make long enough paths for programs
to fail. By using `Unlinkat` and `Openat` syscalls instead, RemoveAll is
safer on unix systems. Initially implemented for linux, darwin, and several bsds.
Fixes#27029
Co-authored-by: Giuseppe Capizzi <gcapizzi@pivotal.io>
Co-authored-by: Julia Nedialkova <yulia.nedyalkova@sap.com>
Change-Id: Id9fcdf4775962b021b7ff438dc51ee6d16bb5f56
GitHub-Last-Rev: b30a621fe3
GitHub-Pull-Request: golang/go#27871
Reviewed-on: https://go-review.googlesource.com/c/137442
Run-TryBot: Ian Lance Taylor <iant@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
The wait was there, because we discovered that we could not remove
finished process executable without the wait on Windows XP. But
Windows XP is not supported by Go. Maybe we do not need the wait
with modern Windows versions. Remove the sleep.
Fixes#25965
Change-Id: I02094abee3592ce4fea98eaff9d15137dc54dc81
Reviewed-on: https://go-review.googlesource.com/c/145221
Run-TryBot: Alex Brainman <alex.brainman@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
We achieve this by always running all tests that create files in a
fresh temporary directory, rather than just on darwin/{arm,arm64}.
As a bonus, this lets us simplify the cleanup code for these tests
and assume their working directory starts out empty.
Updates #28387
Change-Id: I952007ae390a2451c9a368da26c7f9f5af64b2ba
Reviewed-on: https://go-review.googlesource.com/c/145283
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
This commits fixes tests for AIX inside os package.
"hostname" command on AIX returns "name.domain" and not only "name".
So, "hostname -s" must be called.
Change-Id: I75e193bcb6ad607ce54ad99aabbed9839012f707
Reviewed-on: https://go-review.googlesource.com/c/144537
Reviewed-by: Tobias Klauser <tobias.klauser@gmail.com>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
Run-TryBot: Tobias Klauser <tobias.klauser@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Otherwise, if the test is run in the background, it will stop waiting
for access to the terminal.
Change-Id: Ib5224c6cb9060281e05c3b00cd2964445421e774
Reviewed-on: https://go-review.googlesource.com/c/136415
Run-TryBot: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>