blob: 890652ca836a8ba3875ddd9531796a3f9191d34e [file] [log] [blame]
khenaidooac637102019-01-14 15:44:34 -05001// 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_darwin.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 unix
389
390import (
391 "syscall"
392 "unsafe"
393)
394
395var _ syscall.Errno
396
397%s
398`