1// Copyright 2012 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package runtime
6
7import "unsafe"
8
9const (
10	// Plan 9 environment device
11	envDir = "/env/"
12	// size of buffer to read from a directory
13	dirBufSize = 4096
14	// size of buffer to read an environment variable (may grow)
15	envBufSize = 128
16	// offset of the name field in a 9P directory entry - see syscall.UnmarshalDir()
17	nameOffset = 39
18)
19
20// goenvs caches the Plan 9 environment variables at start of execution into
21// string array envs, to supply the initial contents for os.Environ.
22// Subsequent calls to os.Setenv will change this cache, without writing back
23// to the (possibly shared) Plan 9 environment, so that Setenv and Getenv
24// conform to the same Posix semantics as on other operating systems.
25// For Plan 9 shared environment semantics, instead of Getenv(key) and
26// Setenv(key, value), one can use os.ReadFile("/env/" + key) and
27// os.WriteFile("/env/" + key, value, 0666) respectively.
28//
29//go:nosplit
30func goenvs() {
31	buf := make([]byte, envBufSize)
32	copy(buf, envDir)
33	dirfd := open(&buf[0], _OREAD, 0)
34	if dirfd < 0 {
35		return
36	}
37	defer closefd(dirfd)
38	dofiles(dirfd, func(name []byte) {
39		name = append(name, 0)
40		buf = buf[:len(envDir)]
41		copy(buf, envDir)
42		buf = append(buf, name...)
43		fd := open(&buf[0], _OREAD, 0)
44		if fd < 0 {
45			return
46		}
47		defer closefd(fd)
48		n := len(buf)
49		r := 0
50		for {
51			r = int(pread(fd, unsafe.Pointer(&buf[0]), int32(n), 0))
52			if r < n {
53				break
54			}
55			n = int(seek(fd, 0, 2)) + 1
56			if len(buf) < n {
57				buf = make([]byte, n)
58			}
59		}
60		if r <= 0 {
61			r = 0
62		} else if buf[r-1] == 0 {
63			r--
64		}
65		name[len(name)-1] = '='
66		env := make([]byte, len(name)+r)
67		copy(env, name)
68		copy(env[len(name):], buf[:r])
69		envs = append(envs, string(env))
70	})
71}
72
73// dofiles reads the directory opened with file descriptor fd, applying function f
74// to each filename in it.
75//
76//go:nosplit
77func dofiles(dirfd int32, f func([]byte)) {
78	dirbuf := new([dirBufSize]byte)
79
80	var off int64 = 0
81	for {
82		n := pread(dirfd, unsafe.Pointer(&dirbuf[0]), int32(dirBufSize), off)
83		if n <= 0 {
84			return
85		}
86		for b := dirbuf[:n]; len(b) > 0; {
87			var name []byte
88			name, b = gdirname(b)
89			if name == nil {
90				return
91			}
92			f(name)
93		}
94		off += int64(n)
95	}
96}
97
98// gdirname returns the first filename from a buffer of directory entries,
99// and a slice containing the remaining directory entries.
100// If the buffer doesn't start with a valid directory entry, the returned name is nil.
101//
102//go:nosplit
103func gdirname(buf []byte) (name []byte, rest []byte) {
104	if 2+nameOffset+2 > len(buf) {
105		return
106	}
107	entryLen, buf := gbit16(buf)
108	if entryLen > len(buf) {
109		return
110	}
111	n, b := gbit16(buf[nameOffset:])
112	if n > len(b) {
113		return
114	}
115	name = b[:n]
116	rest = buf[entryLen:]
117	return
118}
119
120// gbit16 reads a 16-bit little-endian binary number from b and returns it
121// with the remaining slice of b.
122//
123//go:nosplit
124func gbit16(b []byte) (int, []byte) {
125	return int(b[0]) | int(b[1])<<8, b[2:]
126}
127