| // Copyright 2012 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package runtime |
| |
| import "unsafe" |
| |
| const ( |
| // Plan 9 environment device |
| envDir = "/env/" |
| // size of buffer to read from a directory |
| dirBufSize = 4096 |
| // size of buffer to read an environment variable (may grow) |
| envBufSize = 128 |
| // offset of the name field in a 9P directory entry - see syscall.UnmarshalDir() |
| nameOffset = 39 |
| ) |
| |
| // Goenvs caches the Plan 9 environment variables at start of execution into |
| // string array envs, to supply the initial contents for os.Environ. |
| // Subsequent calls to os.Setenv will change this cache, without writing back |
| // to the (possibly shared) Plan 9 environment, so that Setenv and Getenv |
| // conform to the same Posix semantics as on other operating systems. |
| // For Plan 9 shared environment semantics, instead of Getenv(key) and |
| // Setenv(key, value), one can use os.ReadFile("/env/" + key) and |
| // os.WriteFile("/env/" + key, value, 0666) respectively. |
| // |
| //go:nosplit |
| func goenvs() { |
| buf := make([]byte, envBufSize) |
| copy(buf, envDir) |
| dirfd := open(&buf[0], _OREAD, 0) |
| if dirfd < 0 { |
| return |
| } |
| defer closefd(dirfd) |
| dofiles(dirfd, func(name []byte) { |
| name = append(name, 0) |
| buf = buf[:len(envDir)] |
| copy(buf, envDir) |
| buf = append(buf, name...) |
| fd := open(&buf[0], _OREAD, 0) |
| if fd < 0 { |
| return |
| } |
| defer closefd(fd) |
| n := len(buf) |
| r := 0 |
| for { |
| r = int(pread(fd, unsafe.Pointer(&buf[0]), int32(n), 0)) |
| if r < n { |
| break |
| } |
| n = int(seek(fd, 0, 2)) + 1 |
| if len(buf) < n { |
| buf = make([]byte, n) |
| } |
| } |
| if r <= 0 { |
| r = 0 |
| } else if buf[r-1] == 0 { |
| r-- |
| } |
| name[len(name)-1] = '=' |
| env := make([]byte, len(name)+r) |
| copy(env, name) |
| copy(env[len(name):], buf[:r]) |
| envs = append(envs, string(env)) |
| }) |
| } |
| |
| // Dofiles reads the directory opened with file descriptor fd, applying function f |
| // to each filename in it. |
| // |
| //go:nosplit |
| func dofiles(dirfd int32, f func([]byte)) { |
| dirbuf := new([dirBufSize]byte) |
| |
| var off int64 = 0 |
| for { |
| n := pread(dirfd, unsafe.Pointer(&dirbuf[0]), int32(dirBufSize), off) |
| if n <= 0 { |
| return |
| } |
| for b := dirbuf[:n]; len(b) > 0; { |
| var name []byte |
| name, b = gdirname(b) |
| if name == nil { |
| return |
| } |
| f(name) |
| } |
| off += int64(n) |
| } |
| } |
| |
| // Gdirname returns the first filename from a buffer of directory entries, |
| // and a slice containing the remaining directory entries. |
| // If the buffer doesn't start with a valid directory entry, the returned name is nil. |
| // |
| //go:nosplit |
| func gdirname(buf []byte) (name []byte, rest []byte) { |
| if 2+nameOffset+2 > len(buf) { |
| return |
| } |
| entryLen, buf := gbit16(buf) |
| if entryLen > len(buf) { |
| return |
| } |
| n, b := gbit16(buf[nameOffset:]) |
| if n > len(b) { |
| return |
| } |
| name = b[:n] |
| rest = buf[entryLen:] |
| return |
| } |
| |
| // Gbit16 reads a 16-bit little-endian binary number from b and returns it |
| // with the remaining slice of b. |
| // |
| //go:nosplit |
| func gbit16(b []byte) (int, []byte) { |
| return int(b[0]) | int(b[1])<<8, b[2:] |
| } |