1// Copyright 2018 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
5//go:build ignore
6// +build ignore
7
8/*
9This program reads a file containing function prototypes
10(like syscall_darwin.go) and generates system call bodies.
11The prototypes are marked by lines beginning with "//sys"
12and read like func declarations if //sys is replaced by func, but:
13	* The parameter lists must give a name for each argument.
14	  This includes return parameters.
15	* The parameter lists must give a type for each argument:
16	  the (x, y, z int) shorthand is not allowed.
17	* If the return parameter is an error number, it must be named errno.
18
19A line beginning with //sysnb is like //sys, except that the
20goroutine will not be suspended during the execution of the system
21call.  This must only be used for system calls which can never
22block, as otherwise the system call could cause all goroutines to
23hang.
24*/
25package main
26
27import (
28	"bufio"
29	"flag"
30	"fmt"
31	"os"
32	"regexp"
33	"strings"
34)
35
36var (
37	b32       = flag.Bool("b32", false, "32bit big-endian")
38	l32       = flag.Bool("l32", false, "32bit little-endian")
39	plan9     = flag.Bool("plan9", false, "plan9")
40	openbsd   = flag.Bool("openbsd", false, "openbsd")
41	netbsd    = flag.Bool("netbsd", false, "netbsd")
42	dragonfly = flag.Bool("dragonfly", false, "dragonfly")
43	arm       = flag.Bool("arm", false, "arm") // 64-bit value should use (even, odd)-pair
44	tags      = flag.String("tags", "", "build tags")
45	filename  = flag.String("output", "", "output file name (standard output if omitted)")
46)
47
48// cmdLine returns this programs's commandline arguments
49func cmdLine() string {
50	return "go run mksyscall.go " + strings.Join(os.Args[1:], " ")
51}
52
53// goBuildTags returns build tags in the go:build format.
54func goBuildTags() string {
55	return strings.ReplaceAll(*tags, ",", " && ")
56}
57
58// plusBuildTags returns build tags in the +build format.
59func plusBuildTags() string {
60	return *tags
61}
62
63// Param is function parameter
64type Param struct {
65	Name string
66	Type string
67}
68
69// usage prints the program usage
70func usage() {
71	fmt.Fprintf(os.Stderr, "usage: go run mksyscall.go [-b32 | -l32] [-tags x,y] [file ...]\n")
72	os.Exit(1)
73}
74
75// parseParamList parses parameter list and returns a slice of parameters
76func parseParamList(list string) []string {
77	list = strings.TrimSpace(list)
78	if list == "" {
79		return []string{}
80	}
81	return regexp.MustCompile(`\s*,\s*`).Split(list, -1)
82}
83
84// parseParam splits a parameter into name and type
85func parseParam(p string) Param {
86	ps := regexp.MustCompile(`^(\S*) (\S*)$`).FindStringSubmatch(p)
87	if ps == nil {
88		fmt.Fprintf(os.Stderr, "malformed parameter: %s\n", p)
89		os.Exit(1)
90	}
91	return Param{ps[1], ps[2]}
92}
93
94func main() {
95	goos := os.Getenv("GOOS_TARGET")
96	if goos == "" {
97		goos = os.Getenv("GOOS")
98	}
99	if goos == "" {
100		fmt.Fprintln(os.Stderr, "GOOS not defined in environment")
101		os.Exit(1)
102	}
103
104	// Check that we are using the Docker-based build system if we should
105	if goos == "linux" {
106		if os.Getenv("GOLANG_SYS_BUILD") != "docker" {
107			fmt.Fprintf(os.Stderr, "In the Docker-based build system, mksyscall should not be called directly.\n")
108			fmt.Fprintf(os.Stderr, "See README.md\n")
109			os.Exit(1)
110		}
111	}
112
113	flag.Usage = usage
114	flag.Parse()
115	if len(flag.Args()) <= 0 {
116		fmt.Fprintf(os.Stderr, "no files to parse provided\n")
117		usage()
118	}
119
120	endianness := ""
121	if *b32 {
122		endianness = "big-endian"
123	} else if *l32 {
124		endianness = "little-endian"
125	}
126
127	libc := false
128	if goos == "darwin" {
129		libc = true
130	}
131	trampolines := map[string]bool{}
132
133	text := ""
134	for _, path := range flag.Args() {
135		file, err := os.Open(path)
136		if err != nil {
137			fmt.Fprintf(os.Stderr, err.Error())
138			os.Exit(1)
139		}
140		s := bufio.NewScanner(file)
141		for s.Scan() {
142			t := s.Text()
143			nonblock := regexp.MustCompile(`^\/\/sysnb\t`).FindStringSubmatch(t)
144			if regexp.MustCompile(`^\/\/sys\t`).FindStringSubmatch(t) == nil && nonblock == nil {
145				continue
146			}
147
148			// Line must be of the form
149			//	func Open(path string, mode int, perm int) (fd int, errno error)
150			// Split into name, in params, out params.
151			f := regexp.MustCompile(`^\/\/sys(nb)?\t(\w+)\(([^()]*)\)\s*(?:\(([^()]+)\))?\s*(?:=\s*((?i)SYS_[A-Z0-9_]+))?$`).FindStringSubmatch(t)
152			if f == nil {
153				fmt.Fprintf(os.Stderr, "%s:%s\nmalformed //sys declaration\n", path, t)
154				os.Exit(1)
155			}
156			funct, inps, outps, sysname := f[2], f[3], f[4], f[5]
157
158			// Split argument lists on comma.
159			in := parseParamList(inps)
160			out := parseParamList(outps)
161
162			// Try in vain to keep people from editing this file.
163			// The theory is that they jump into the middle of the file
164			// without reading the header.
165			text += "// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT\n\n"
166
167			// Go function header.
168			outDecl := ""
169			if len(out) > 0 {
170				outDecl = fmt.Sprintf(" (%s)", strings.Join(out, ", "))
171			}
172			text += fmt.Sprintf("func %s(%s)%s {\n", funct, strings.Join(in, ", "), outDecl)
173
174			// Check if err return available
175			errvar := ""
176			for _, param := range out {
177				p := parseParam(param)
178				if p.Type == "error" {
179					errvar = p.Name
180					break
181				}
182			}
183
184			// Prepare arguments to Syscall.
185			var args []string
186			n := 0
187			for _, param := range in {
188				p := parseParam(param)
189				if regexp.MustCompile(`^\*`).FindStringSubmatch(p.Type) != nil {
190					args = append(args, "uintptr(unsafe.Pointer("+p.Name+"))")
191				} else if p.Type == "string" && errvar != "" {
192					text += fmt.Sprintf("\tvar _p%d *byte\n", n)
193					text += fmt.Sprintf("\t_p%d, %s = BytePtrFromString(%s)\n", n, errvar, p.Name)
194					text += fmt.Sprintf("\tif %s != nil {\n\t\treturn\n\t}\n", errvar)
195					args = append(args, fmt.Sprintf("uintptr(unsafe.Pointer(_p%d))", n))
196					n++
197				} else if p.Type == "string" {
198					fmt.Fprintf(os.Stderr, path+":"+funct+" uses string arguments, but has no error return\n")
199					text += fmt.Sprintf("\tvar _p%d *byte\n", n)
200					text += fmt.Sprintf("\t_p%d, _ = BytePtrFromString(%s)\n", n, p.Name)
201					args = append(args, fmt.Sprintf("uintptr(unsafe.Pointer(_p%d))", n))
202					n++
203				} else if regexp.MustCompile(`^\[\](.*)`).FindStringSubmatch(p.Type) != nil {
204					// Convert slice into pointer, length.
205					// Have to be careful not to take address of &a[0] if len == 0:
206					// pass dummy pointer in that case.
207					// Used to pass nil, but some OSes or simulators reject write(fd, nil, 0).
208					text += fmt.Sprintf("\tvar _p%d unsafe.Pointer\n", n)
209					text += fmt.Sprintf("\tif len(%s) > 0 {\n\t\t_p%d = unsafe.Pointer(&%s[0])\n\t}", p.Name, n, p.Name)
210					text += fmt.Sprintf(" else {\n\t\t_p%d = unsafe.Pointer(&_zero)\n\t}\n", n)
211					args = append(args, fmt.Sprintf("uintptr(_p%d)", n), fmt.Sprintf("uintptr(len(%s))", p.Name))
212					n++
213				} else if p.Type == "int64" && (*openbsd || *netbsd) {
214					args = append(args, "0")
215					if endianness == "big-endian" {
216						args = append(args, fmt.Sprintf("uintptr(%s>>32)", p.Name), fmt.Sprintf("uintptr(%s)", p.Name))
217					} else if endianness == "little-endian" {
218						args = append(args, fmt.Sprintf("uintptr(%s)", p.Name), fmt.Sprintf("uintptr(%s>>32)", p.Name))
219					} else {
220						args = append(args, fmt.Sprintf("uintptr(%s)", p.Name))
221					}
222				} else if p.Type == "int64" && *dragonfly {
223					if regexp.MustCompile(`^(?i)extp(read|write)`).FindStringSubmatch(funct) == nil {
224						args = append(args, "0")
225					}
226					if endianness == "big-endian" {
227						args = append(args, fmt.Sprintf("uintptr(%s>>32)", p.Name), fmt.Sprintf("uintptr(%s)", p.Name))
228					} else if endianness == "little-endian" {
229						args = append(args, fmt.Sprintf("uintptr(%s)", p.Name), fmt.Sprintf("uintptr(%s>>32)", p.Name))
230					} else {
231						args = append(args, fmt.Sprintf("uintptr(%s)", p.Name))
232					}
233				} else if (p.Type == "int64" || p.Type == "uint64") && endianness != "" {
234					if len(args)%2 == 1 && *arm {
235						// arm abi specifies 64-bit argument uses
236						// (even, odd) pair
237						args = append(args, "0")
238					}
239					if endianness == "big-endian" {
240						args = append(args, fmt.Sprintf("uintptr(%s>>32)", p.Name), fmt.Sprintf("uintptr(%s)", p.Name))
241					} else {
242						args = append(args, fmt.Sprintf("uintptr(%s)", p.Name), fmt.Sprintf("uintptr(%s>>32)", p.Name))
243					}
244				} else {
245					args = append(args, fmt.Sprintf("uintptr(%s)", p.Name))
246				}
247			}
248
249			// Determine which form to use; pad args with zeros.
250			asm := "Syscall"
251			if nonblock != nil {
252				if errvar == "" && goos == "linux" {
253					asm = "RawSyscallNoError"
254				} else {
255					asm = "RawSyscall"
256				}
257			} else {
258				if errvar == "" && goos == "linux" {
259					asm = "SyscallNoError"
260				}
261			}
262			if len(args) <= 3 {
263				for len(args) < 3 {
264					args = append(args, "0")
265				}
266			} else if len(args) <= 6 {
267				asm += "6"
268				for len(args) < 6 {
269					args = append(args, "0")
270				}
271			} else if len(args) <= 9 {
272				asm += "9"
273				for len(args) < 9 {
274					args = append(args, "0")
275				}
276			} else {
277				fmt.Fprintf(os.Stderr, "%s:%s too many arguments to system call\n", path, funct)
278			}
279
280			// System call number.
281			if sysname == "" {
282				sysname = "SYS_" + funct
283				sysname = regexp.MustCompile(`([a-z])([A-Z])`).ReplaceAllString(sysname, `${1}_$2`)
284				sysname = strings.ToUpper(sysname)
285			}
286
287			var libcFn string
288			if libc {
289				asm = "syscall_" + strings.ToLower(asm[:1]) + asm[1:] // internal syscall call
290				sysname = strings.TrimPrefix(sysname, "SYS_")         // remove SYS_
291				sysname = strings.ToLower(sysname)                    // lowercase
292				libcFn = sysname
293				sysname = "libc_" + sysname + "_trampoline_addr"
294			}
295
296			// Actual call.
297			arglist := strings.Join(args, ", ")
298			call := fmt.Sprintf("%s(%s, %s)", asm, sysname, arglist)
299
300			// Assign return values.
301			body := ""
302			ret := []string{"_", "_", "_"}
303			doErrno := false
304			for i := 0; i < len(out); i++ {
305				p := parseParam(out[i])
306				reg := ""
307				if p.Name == "err" && !*plan9 {
308					reg = "e1"
309					ret[2] = reg
310					doErrno = true
311				} else if p.Name == "err" && *plan9 {
312					ret[0] = "r0"
313					ret[2] = "e1"
314					break
315				} else {
316					reg = fmt.Sprintf("r%d", i)
317					ret[i] = reg
318				}
319				if p.Type == "bool" {
320					reg = fmt.Sprintf("%s != 0", reg)
321				}
322				if p.Type == "int64" && endianness != "" {
323					// 64-bit number in r1:r0 or r0:r1.
324					if i+2 > len(out) {
325						fmt.Fprintf(os.Stderr, "%s:%s not enough registers for int64 return\n", path, funct)
326					}
327					if endianness == "big-endian" {
328						reg = fmt.Sprintf("int64(r%d)<<32 | int64(r%d)", i, i+1)
329					} else {
330						reg = fmt.Sprintf("int64(r%d)<<32 | int64(r%d)", i+1, i)
331					}
332					ret[i] = fmt.Sprintf("r%d", i)
333					ret[i+1] = fmt.Sprintf("r%d", i+1)
334				}
335				if reg != "e1" || *plan9 {
336					body += fmt.Sprintf("\t%s = %s(%s)\n", p.Name, p.Type, reg)
337				}
338			}
339			if ret[0] == "_" && ret[1] == "_" && ret[2] == "_" {
340				text += fmt.Sprintf("\t%s\n", call)
341			} else {
342				if errvar == "" && goos == "linux" {
343					// raw syscall without error on Linux, see golang.org/issue/22924
344					text += fmt.Sprintf("\t%s, %s := %s\n", ret[0], ret[1], call)
345				} else {
346					text += fmt.Sprintf("\t%s, %s, %s := %s\n", ret[0], ret[1], ret[2], call)
347				}
348			}
349			text += body
350
351			if *plan9 && ret[2] == "e1" {
352				text += "\tif int32(r0) == -1 {\n"
353				text += "\t\terr = e1\n"
354				text += "\t}\n"
355			} else if doErrno {
356				text += "\tif e1 != 0 {\n"
357				text += "\t\terr = errnoErr(e1)\n"
358				text += "\t}\n"
359			}
360			text += "\treturn\n"
361			text += "}\n\n"
362
363			if libc && !trampolines[libcFn] {
364				// some system calls share a trampoline, like read and readlen.
365				trampolines[libcFn] = true
366				// Declare assembly trampoline address.
367				text += fmt.Sprintf("var libc_%s_trampoline_addr uintptr\n\n", libcFn)
368				// Assembly trampoline calls the libc_* function, which this magic
369				// redirects to use the function from libSystem.
370				text += fmt.Sprintf("//go:cgo_import_dynamic libc_%s %s \"/usr/lib/libSystem.B.dylib\"\n", libcFn, libcFn)
371				text += "\n"
372			}
373		}
374		if err := s.Err(); err != nil {
375			fmt.Fprintf(os.Stderr, err.Error())
376			os.Exit(1)
377		}
378		file.Close()
379	}
380	fmt.Printf(srcTemplate, cmdLine(), goBuildTags(), plusBuildTags(), text)
381}
382
383const srcTemplate = `// %s
384// Code generated by the command above; see README.md. DO NOT EDIT.
385
386//go:build %s
387// +build %s
388
389package unix
390
391import (
392	"syscall"
393	"unsafe"
394)
395
396var _ syscall.Errno
397
398%s
399`
400