diff --git a/src/os/env_test.go b/src/os/env_test.go index 40721b63ec..8489a976c5 100644 --- a/src/os/env_test.go +++ b/src/os/env_test.go @@ -122,7 +122,33 @@ func TestUnsetenv(t *testing.T) { } } -// TODO: add back TestClearenv() and fix the errors it finds +func TestClearenv(t *testing.T) { + const testKey = "GO_TEST_CLEARENV" + const testValue = "1" + + // reset env + defer func(origEnv []string) { + for _, pair := range origEnv { + // Environment variables on Windows can begin with = + // https://blogs.msdn.com/b/oldnewthing/archive/2010/05/06/10008132.aspx + i := strings.Index(pair[1:], "=") + 1 + if err := Setenv(pair[:i], pair[i+1:]); err != nil { + t.Errorf("Setenv(%q, %q) failed during reset: %v", pair[:i], pair[i+1:], err) + } + } + }(Environ()) + + if err := Setenv(testKey, testValue); err != nil { + t.Fatalf("Setenv(%q, %q) failed: %v", testKey, testValue, err) + } + if _, ok := LookupEnv(testKey); !ok { + t.Errorf("Setenv(%q, %q) didn't set $%s", testKey, testValue, testKey) + } + Clearenv() + if val, ok := LookupEnv(testKey); ok { + t.Errorf("Clearenv() didn't clear $%s, remained with value %q", testKey, val) + } +} func TestLookupEnv(t *testing.T) { const smallpox = "SMALLPOX" // No one has smallpox. diff --git a/src/syscall/syscall_libc.go b/src/syscall/syscall_libc.go index da46b9ffb4..0ef64984a2 100644 --- a/src/syscall/syscall_libc.go +++ b/src/syscall/syscall_libc.go @@ -235,11 +235,24 @@ func Mprotect(b []byte, prot int) (err error) { } func Environ() []string { - environ := libc_environ - var envs []string - for *environ != nil { - // Convert the C string to a Go string. - length := libc_strlen(*environ) + // calculate total memory required + var length uintptr + var vars int + for environ := libc_environ; *environ != nil; { + length += libc_strlen(*environ) + vars++ + environ = (*unsafe.Pointer)(unsafe.Pointer(uintptr(unsafe.Pointer(environ)) + unsafe.Sizeof(environ))) + } + + // allocate our backing slice for the strings + b := make([]byte, length) + // and the slice we're going to return + envs := make([]string, 0, vars) + + // loop over the environment again, this time copying over the data to the backing slice + for environ := libc_environ; *environ != nil; { + length = libc_strlen(*environ) + // construct a Go string pointing at the libc-allocated environment variable data var envVar string rawEnvVar := (*struct { ptr unsafe.Pointer @@ -247,8 +260,16 @@ func Environ() []string { })(unsafe.Pointer(&envVar)) rawEnvVar.ptr = *environ rawEnvVar.length = length - envs = append(envs, envVar) - // This is the Go equivalent of "environ++" in C. + // pull off the number of bytes we need for this environment variable + var bs []byte + bs, b = b[:length], b[length:] + // copy over the bytes to the Go heap + copy(bs, envVar) + // convert trimmed slice to string + s := *(*string)(unsafe.Pointer(&bs)) + // add s to our list of environment variables + envs = append(envs, s) + // environ++ environ = (*unsafe.Pointer)(unsafe.Pointer(uintptr(unsafe.Pointer(environ)) + unsafe.Sizeof(environ))) } return envs