Skip to content

Commit

Permalink
os, syscall: add Unsetenv
Browse files Browse the repository at this point in the history
Also address a TODO, making Clearenv pass through to cgo.

Based largely on Minux's earlier https://golang.org/cl/82040044

Fixes golang#6423

LGTM=iant, alex.brainman, r, rsc
R=rsc, iant, r, alex.brainman
CC=golang-codereviews
https://golang.org/cl/148370043
  • Loading branch information
bradfitz authored and wheatman committed Jul 6, 2018
1 parent 5abb5a3 commit ad2a3dd
Show file tree
Hide file tree
Showing 9 changed files with 135 additions and 12 deletions.
5 changes: 5 additions & 0 deletions src/os/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,11 @@ func Setenv(key, value string) error {
return nil
}

// Unsetenv unsets a single environment variable.
func Unsetenv(key string) error {
return syscall.Unsetenv(key)
}

// Clearenv deletes all environment variables.
func Clearenv() {
syscall.Clearenv()
Expand Down
26 changes: 26 additions & 0 deletions src/os/env_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package os_test
import (
. "os"
"reflect"
"strings"
"testing"
)

Expand Down Expand Up @@ -68,3 +69,28 @@ func TestConsistentEnviron(t *testing.T) {
}
}
}

func TestUnsetenv(t *testing.T) {
const testKey = "GO_TEST_UNSETENV"
set := func() bool {
prefix := testKey + "="
for _, key := range Environ() {
if strings.HasPrefix(key, prefix) {
return true
}
}
return false
}
if err := Setenv(testKey, "1"); err != nil {
t.Fatalf("Setenv: %v", err)
}
if !set() {
t.Error("Setenv didn't set TestUnsetenv")
}
if err := Unsetenv(testKey); err != nil {
t.Fatalf("Unsetenv: %v", err)
}
if set() {
t.Fatal("Unsetenv didn't clear TestUnsetenv")
}
}
7 changes: 7 additions & 0 deletions src/runtime/cgo/gcc_setenv.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,10 @@ x_cgo_setenv(char **arg)
{
setenv(arg[0], arg[1], 1);
}

/* Stub for calling unsetenv */
void
x_cgo_unsetenv(char *arg)
{
unsetenv(arg);
}
3 changes: 3 additions & 0 deletions src/runtime/cgo/setenv.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
// +build akaros darwin dragonfly freebsd linux netbsd openbsd

#pragma cgo_import_static x_cgo_setenv
#pragma cgo_import_static x_cgo_unsetenv

void x_cgo_setenv(char**);
void (*runtime·_cgo_setenv)(char**) = x_cgo_setenv;
void x_cgo_unsetenv(char**);
void (*runtime·_cgo_unsetenv)(char**) = x_cgo_unsetenv;
13 changes: 12 additions & 1 deletion src/runtime/env_posix.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ func gogetenv(key string) string {
return ""
}

var _cgo_setenv uintptr // pointer to C function
var _cgo_setenv uintptr // pointer to C function
var _cgo_unsetenv uintptr // pointer to C function

// Update the C environment if cgo is loaded.
// Called from syscall.Setenv.
Expand All @@ -44,6 +45,16 @@ func syscall_setenv_c(k string, v string) {
asmcgocall(unsafe.Pointer(_cgo_setenv), unsafe.Pointer(&arg))
}

// Update the C environment if cgo is loaded.
// Called from syscall.unsetenv.
func syscall_unsetenv_c(k string) {
if _cgo_unsetenv == 0 {
return
}
arg := [1]unsafe.Pointer{cstring(k)}
asmcgocall(unsafe.Pointer(_cgo_unsetenv), unsafe.Pointer(&arg))
}

func cstring(s string) unsafe.Pointer {
p := make([]byte, len(s)+1)
sp := (*_string)(unsafe.Pointer(&s))
Expand Down
3 changes: 3 additions & 0 deletions src/runtime/thunk.s
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,9 @@ TEXT net·runtime_pollUnblock(SB),NOSPLIT,$0-0
TEXT syscall·setenv_c(SB), NOSPLIT, $0-0
JMP runtime·syscall_setenv_c(SB)

TEXT syscall·unsetenv_c(SB), NOSPLIT, $0-0
JMP runtime·syscall_unsetenv_c(SB)

TEXT reflect·makemap(SB),NOSPLIT,$0-0
JMP runtime·reflect_makemap(SB)

Expand Down
38 changes: 35 additions & 3 deletions src/syscall/env_plan9.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,22 @@ import (
)

var (
// envOnce guards copyenv, which populates env.
// envOnce guards copyenv, which populates env, envi and envs.
envOnce sync.Once

// envLock guards env and envs.
// envLock guards env, envi and envs.
envLock sync.RWMutex

// env maps from an environment variable to its value.
// TODO: remove this? golang.org/issue/8849
env = make(map[string]string)

// envi maps from an environment variable to its index in envs.
// TODO: remove this? golang.org/issue/8849
envi = make(map[string]int)

// envs contains elements of env in the form "key=value".
// empty strings mean deleted.
envs []string

errZeroLengthKey = errors.New("zero length key")
Expand Down Expand Up @@ -83,6 +89,7 @@ func copyenv() {
}
env[key] = v
envs[i] = key + "=" + v
envi[key] = i
i++
}
}
Expand Down Expand Up @@ -129,14 +136,39 @@ func Clearenv() {
defer envLock.Unlock()

env = make(map[string]string)
envi = make(map[string]int)
envs = []string{}
RawSyscall(SYS_RFORK, RFCENVG, 0, 0)
}

func Unsetenv(key string) error {
if len(key) == 0 {
return errZeroLengthKey
}

envLock.Lock()
defer envLock.Unlock()

Remove("/env/" + key)

if i, ok := envi[key]; ok {
delete(env, key)
delete(envi, key)
envs[i] = ""
}
return nil
}

func Environ() []string {
envLock.RLock()
defer envLock.RUnlock()

envOnce.Do(copyenv)
return append([]string(nil), envs...)
ret := make([]string, 0, len(envs))
for _, pair := range envs {
if pair != "" {
ret = append(ret, pair)
}
}
return ret
}
44 changes: 36 additions & 8 deletions src/syscall/env_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,18 @@ var (
// env maps from an environment variable to its first occurrence in envs.
env map[string]int

// envs is provided by the runtime. elements are expected to be
// of the form "key=value".
// envs is provided by the runtime. elements are expected to
// be of the form "key=value". An empty string means deleted
// (or a duplicate to be ignored).
envs []string = runtime_envs()
)

func runtime_envs() []string // in package runtime

// setenv_c is provided by the runtime, but is a no-op if cgo isn't
// loaded.
// setenv_c and unsetenv_c are provided by the runtime but are no-ops
// if cgo isn't loaded.
func setenv_c(k, v string)
func unsetenv_c(k string)

func copyenv() {
env = make(map[string]int)
Expand All @@ -38,14 +40,34 @@ func copyenv() {
if s[j] == '=' {
key := s[:j]
if _, ok := env[key]; !ok {
env[key] = i
env[key] = i // first mention of key
} else {
// Clear duplicate keys. This permits Unsetenv to
// safely delete only the first item without
// worrying about unshadowing a later one,
// which might be a security problem.
envs[i] = ""
}
break
}
}
}
}

func Unsetenv(key string) error {
envOnce.Do(copyenv)

envLock.Lock()
defer envLock.Unlock()

if i, ok := env[key]; ok {
envs[i] = ""
delete(env, key)
}
unsetenv_c(key)
return nil
}

func Getenv(key string) (value string, found bool) {
envOnce.Do(copyenv)
if len(key) == 0 {
Expand Down Expand Up @@ -106,16 +128,22 @@ func Clearenv() {
envLock.Lock()
defer envLock.Unlock()

for k := range env {
unsetenv_c(k)
}
env = make(map[string]int)
envs = []string{}
// TODO(bradfitz): pass through to C
}

func Environ() []string {
envOnce.Do(copyenv)
envLock.RLock()
defer envLock.RUnlock()
a := make([]string, len(envs))
copy(a, envs)
a := make([]string, 0, len(envs))
for _, env := range envs {
if env != "" {
a = append(a, env)
}
}
return a
}
8 changes: 8 additions & 0 deletions src/syscall/env_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,14 @@ func Setenv(key, value string) error {
return nil
}

func Unsetenv(key string) error {
keyp, err := UTF16PtrFromString(key)
if err != nil {
return err
}
return SetEnvironmentVariable(keyp, nil)
}

func Clearenv() {
for _, s := range Environ() {
// Environment variables can begin with =
Expand Down

0 comments on commit ad2a3dd

Please sign in to comment.