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