Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion src/os/env_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
35 changes: 28 additions & 7 deletions src/syscall/syscall_libc.go
Original file line number Diff line number Diff line change
Expand Up @@ -235,20 +235,41 @@ 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
length uintptr
})(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))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a dirty trick... but I have to admit, this whole function isn't exactly clean and I wrote it originally.
(Dirty because it's not guaranteed that slice/string layout will stay the way it is now - although I don't see why it would need to change).

// 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
Expand Down