|  | // 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:] | 
|  | } |