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