]> Cypherpunks repositories - gostls13.git/commitdiff
os, syscall: add Unsetenv
authorBrad Fitzpatrick <bradfitz@golang.org>
Wed, 1 Oct 2014 18:17:15 +0000 (11:17 -0700)
committerBrad Fitzpatrick <bradfitz@golang.org>
Wed, 1 Oct 2014 18:17:15 +0000 (11:17 -0700)
Also address a TODO, making Clearenv pass through to cgo.

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

Fixes #6423

LGTM=iant, alex.brainman, r, rsc
R=rsc, iant, r, alex.brainman
CC=golang-codereviews
https://golang.org/cl/148370043

src/os/env.go
src/os/env_test.go
src/runtime/cgo/gcc_setenv.c
src/runtime/cgo/setenv.c
src/runtime/env_posix.go
src/runtime/thunk.s
src/syscall/env_plan9.go
src/syscall/env_unix.go
src/syscall/env_windows.go

index db7fc72b8a4d11d4993d1aba53994c86422760b9..d0494a476340d8f80e117a7e029cc9673c626854 100644 (file)
@@ -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()
index 991fa4d057802a26750d0a43b52652f9b5140ec6..e618067513725fd81843d5271fc7325344577272 100644 (file)
@@ -7,6 +7,7 @@ package os_test
 import (
        . "os"
        "reflect"
+       "strings"
        "testing"
 )
 
@@ -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")
+       }
+}
index 8b128b94651ecf096f43899f4556d014e54f3168..af0fc5d8d8c71eed18cce7b90c6bc1b4c595b4c2 100644 (file)
@@ -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);
+}
index ee529904f7526fdb23f3732c4aeb3404e46a40c1..76d88cbf13c6d5864896671ceeaa7fba97a193d8 100644 (file)
@@ -5,6 +5,9 @@
 // +build 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;
index 6c04f6cc7056bd041cb7f02d79150516dec8fea3..dd57872d7c7affff8e893101cf899350424cbebf 100644 (file)
@@ -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.
@@ -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))
index d6a2d399e6c136fe9581c41479a744a2e4f9e235..0a0f147c4b45aee91cf3f32bc54d797a61b6c9ce 100644 (file)
@@ -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)
 
index 9587ab5af9d21796cb7d73dba70bce3eebdd9b01..3044b410a9e2ced501c14afb07a64c749d09bb92 100644 (file)
@@ -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")
@@ -83,6 +89,7 @@ func copyenv() {
                }
                env[key] = v
                envs[i] = key + "=" + v
+               envi[key] = i
                i++
        }
 }
@@ -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
 }
index 01ac38af13ced11cd829eb94ee1c454d2bcf829c..b5ded9c763c2541d02c87b50f652f23503781350 100644 (file)
@@ -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)
@@ -38,7 +40,13 @@ 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
                        }
@@ -46,6 +54,20 @@ func copyenv() {
        }
 }
 
+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 {
@@ -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
 }
index 420b3872464cbf5bfb05d59a21af9cfe2abffda6..bc21690d9fd70e583f8fc460b99bc3ff292f7d86 100644 (file)
@@ -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 =