blob: 3d864738b69594aaf1b7e60e6c191ac9168039b2 [file] [log] [blame]
Don Newton98fd8812019-09-23 15:15:02 -04001// Copyright 2019 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/*
8 This program reads a file containing function prototypes
9 (like syscall_solaris.go) and generates system call bodies.
10 The prototypes are marked by lines beginning with "//sys"
11 and 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 err.
17 * If go func name needs to be different than its libc name,
18 * or the function is not in libc, name could be specified
19 * at the end, after "=" sign, like
20 //sys getsockopt(s int, level int, name int, val uintptr, vallen *_Socklen) (err error) = libsocket.getsockopt
21*/
22
23package main
24
25import (
26 "bufio"
27 "flag"
28 "fmt"
29 "os"
30 "regexp"
31 "strings"
32)
33
34var (
35 b32 = flag.Bool("b32", false, "32bit big-endian")
36 l32 = flag.Bool("l32", false, "32bit little-endian")
37 tags = flag.String("tags", "", "build tags")
38)
39
40// cmdLine returns this programs's commandline arguments
41func cmdLine() string {
42 return "go run mksyscall_solaris.go " + strings.Join(os.Args[1:], " ")
43}
44
45// buildTags returns build tags
46func buildTags() string {
47 return *tags
48}
49
50// Param is function parameter
51type Param struct {
52 Name string
53 Type string
54}
55
56// usage prints the program usage
57func usage() {
58 fmt.Fprintf(os.Stderr, "usage: go run mksyscall_solaris.go [-b32 | -l32] [-tags x,y] [file ...]\n")
59 os.Exit(1)
60}
61
62// parseParamList parses parameter list and returns a slice of parameters
63func parseParamList(list string) []string {
64 list = strings.TrimSpace(list)
65 if list == "" {
66 return []string{}
67 }
68 return regexp.MustCompile(`\s*,\s*`).Split(list, -1)
69}
70
71// parseParam splits a parameter into name and type
72func parseParam(p string) Param {
73 ps := regexp.MustCompile(`^(\S*) (\S*)$`).FindStringSubmatch(p)
74 if ps == nil {
75 fmt.Fprintf(os.Stderr, "malformed parameter: %s\n", p)
76 os.Exit(1)
77 }
78 return Param{ps[1], ps[2]}
79}
80
81func main() {
82 flag.Usage = usage
83 flag.Parse()
84 if len(flag.Args()) <= 0 {
85 fmt.Fprintf(os.Stderr, "no files to parse provided\n")
86 usage()
87 }
88
89 endianness := ""
90 if *b32 {
91 endianness = "big-endian"
92 } else if *l32 {
93 endianness = "little-endian"
94 }
95
96 pack := ""
97 text := ""
98 dynimports := ""
99 linknames := ""
100 var vars []string
101 for _, path := range flag.Args() {
102 file, err := os.Open(path)
103 if err != nil {
104 fmt.Fprintf(os.Stderr, err.Error())
105 os.Exit(1)
106 }
107 s := bufio.NewScanner(file)
108 for s.Scan() {
109 t := s.Text()
110 t = strings.TrimSpace(t)
111 t = regexp.MustCompile(`\s+`).ReplaceAllString(t, ` `)
112 if p := regexp.MustCompile(`^package (\S+)$`).FindStringSubmatch(t); p != nil && pack == "" {
113 pack = p[1]
114 }
115 nonblock := regexp.MustCompile(`^\/\/sysnb `).FindStringSubmatch(t)
116 if regexp.MustCompile(`^\/\/sys `).FindStringSubmatch(t) == nil && nonblock == nil {
117 continue
118 }
119
120 // Line must be of the form
121 // func Open(path string, mode int, perm int) (fd int, err error)
122 // Split into name, in params, out params.
123 f := regexp.MustCompile(`^\/\/sys(nb)? (\w+)\(([^()]*)\)\s*(?:\(([^()]+)\))?\s*(?:=\s*(?:(\w*)\.)?(\w*))?$`).FindStringSubmatch(t)
124 if f == nil {
125 fmt.Fprintf(os.Stderr, "%s:%s\nmalformed //sys declaration\n", path, t)
126 os.Exit(1)
127 }
128 funct, inps, outps, modname, sysname := f[2], f[3], f[4], f[5], f[6]
129
130 // Split argument lists on comma.
131 in := parseParamList(inps)
132 out := parseParamList(outps)
133
134 inps = strings.Join(in, ", ")
135 outps = strings.Join(out, ", ")
136
137 // Try in vain to keep people from editing this file.
138 // The theory is that they jump into the middle of the file
139 // without reading the header.
140 text += "// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT\n\n"
141
142 // So file name.
143 if modname == "" {
144 modname = "libc"
145 }
146
147 // System call name.
148 if sysname == "" {
149 sysname = funct
150 }
151
152 // System call pointer variable name.
153 sysvarname := fmt.Sprintf("proc%s", sysname)
154
155 strconvfunc := "BytePtrFromString"
156 strconvtype := "*byte"
157
158 sysname = strings.ToLower(sysname) // All libc functions are lowercase.
159
160 // Runtime import of function to allow cross-platform builds.
161 dynimports += fmt.Sprintf("//go:cgo_import_dynamic libc_%s %s \"%s.so\"\n", sysname, sysname, modname)
162 // Link symbol to proc address variable.
163 linknames += fmt.Sprintf("//go:linkname %s libc_%s\n", sysvarname, sysname)
164 // Library proc address variable.
165 vars = append(vars, sysvarname)
166
167 // Go function header.
168 outlist := strings.Join(out, ", ")
169 if outlist != "" {
170 outlist = fmt.Sprintf(" (%s)", outlist)
171 }
172 if text != "" {
173 text += "\n"
174 }
175 text += fmt.Sprintf("func %s(%s)%s {\n", funct, strings.Join(in, ", "), outlist)
176
177 // Check if err return available
178 errvar := ""
179 for _, param := range out {
180 p := parseParam(param)
181 if p.Type == "error" {
182 errvar = p.Name
183 continue
184 }
185 }
186
187 // Prepare arguments to Syscall.
188 var args []string
189 n := 0
190 for _, param := range in {
191 p := parseParam(param)
192 if regexp.MustCompile(`^\*`).FindStringSubmatch(p.Type) != nil {
193 args = append(args, "uintptr(unsafe.Pointer("+p.Name+"))")
194 } else if p.Type == "string" && errvar != "" {
195 text += fmt.Sprintf("\tvar _p%d %s\n", n, strconvtype)
196 text += fmt.Sprintf("\t_p%d, %s = %s(%s)\n", n, errvar, strconvfunc, p.Name)
197 text += fmt.Sprintf("\tif %s != nil {\n\t\treturn\n\t}\n", errvar)
198 args = append(args, fmt.Sprintf("uintptr(unsafe.Pointer(_p%d))", n))
199 n++
200 } else if p.Type == "string" {
201 fmt.Fprintf(os.Stderr, path+":"+funct+" uses string arguments, but has no error return\n")
202 text += fmt.Sprintf("\tvar _p%d %s\n", n, strconvtype)
203 text += fmt.Sprintf("\t_p%d, _ = %s(%s)\n", n, strconvfunc, p.Name)
204 args = append(args, fmt.Sprintf("uintptr(unsafe.Pointer(_p%d))", n))
205 n++
206 } else if s := regexp.MustCompile(`^\[\](.*)`).FindStringSubmatch(p.Type); s != nil {
207 // Convert slice into pointer, length.
208 // Have to be careful not to take address of &a[0] if len == 0:
209 // pass nil in that case.
210 text += fmt.Sprintf("\tvar _p%d *%s\n", n, s[1])
211 text += fmt.Sprintf("\tif len(%s) > 0 {\n\t\t_p%d = &%s[0]\n\t}\n", p.Name, n, p.Name)
212 args = append(args, fmt.Sprintf("uintptr(unsafe.Pointer(_p%d))", n), fmt.Sprintf("uintptr(len(%s))", p.Name))
213 n++
214 } else if p.Type == "int64" && endianness != "" {
215 if endianness == "big-endian" {
216 args = append(args, fmt.Sprintf("uintptr(%s>>32)", p.Name), fmt.Sprintf("uintptr(%s)", p.Name))
217 } else {
218 args = append(args, fmt.Sprintf("uintptr(%s)", p.Name), fmt.Sprintf("uintptr(%s>>32)", p.Name))
219 }
220 } else if p.Type == "bool" {
221 text += fmt.Sprintf("\tvar _p%d uint32\n", n)
222 text += fmt.Sprintf("\tif %s {\n\t\t_p%d = 1\n\t} else {\n\t\t_p%d = 0\n\t}\n", p.Name, n, n)
223 args = append(args, fmt.Sprintf("uintptr(_p%d)", n))
224 n++
225 } else {
226 args = append(args, fmt.Sprintf("uintptr(%s)", p.Name))
227 }
228 }
229 nargs := len(args)
230
231 // Determine which form to use; pad args with zeros.
232 asm := "sysvicall6"
233 if nonblock != nil {
234 asm = "rawSysvicall6"
235 }
236 if len(args) <= 6 {
237 for len(args) < 6 {
238 args = append(args, "0")
239 }
240 } else {
241 fmt.Fprintf(os.Stderr, "%s: too many arguments to system call\n", path)
242 os.Exit(1)
243 }
244
245 // Actual call.
246 arglist := strings.Join(args, ", ")
247 call := fmt.Sprintf("%s(uintptr(unsafe.Pointer(&%s)), %d, %s)", asm, sysvarname, nargs, arglist)
248
249 // Assign return values.
250 body := ""
251 ret := []string{"_", "_", "_"}
252 doErrno := false
253 for i := 0; i < len(out); i++ {
254 p := parseParam(out[i])
255 reg := ""
256 if p.Name == "err" {
257 reg = "e1"
258 ret[2] = reg
259 doErrno = true
260 } else {
261 reg = fmt.Sprintf("r%d", i)
262 ret[i] = reg
263 }
264 if p.Type == "bool" {
265 reg = fmt.Sprintf("%d != 0", reg)
266 }
267 if p.Type == "int64" && endianness != "" {
268 // 64-bit number in r1:r0 or r0:r1.
269 if i+2 > len(out) {
270 fmt.Fprintf(os.Stderr, "%s: not enough registers for int64 return\n", path)
271 os.Exit(1)
272 }
273 if endianness == "big-endian" {
274 reg = fmt.Sprintf("int64(r%d)<<32 | int64(r%d)", i, i+1)
275 } else {
276 reg = fmt.Sprintf("int64(r%d)<<32 | int64(r%d)", i+1, i)
277 }
278 ret[i] = fmt.Sprintf("r%d", i)
279 ret[i+1] = fmt.Sprintf("r%d", i+1)
280 }
281 if reg != "e1" {
282 body += fmt.Sprintf("\t%s = %s(%s)\n", p.Name, p.Type, reg)
283 }
284 }
285 if ret[0] == "_" && ret[1] == "_" && ret[2] == "_" {
286 text += fmt.Sprintf("\t%s\n", call)
287 } else {
288 text += fmt.Sprintf("\t%s, %s, %s := %s\n", ret[0], ret[1], ret[2], call)
289 }
290 text += body
291
292 if doErrno {
293 text += "\tif e1 != 0 {\n"
294 text += "\t\terr = e1\n"
295 text += "\t}\n"
296 }
297 text += "\treturn\n"
298 text += "}\n"
299 }
300 if err := s.Err(); err != nil {
301 fmt.Fprintf(os.Stderr, err.Error())
302 os.Exit(1)
303 }
304 file.Close()
305 }
306 imp := ""
307 if pack != "unix" {
308 imp = "import \"golang.org/x/sys/unix\"\n"
309
310 }
311 vardecls := "\t" + strings.Join(vars, ",\n\t")
312 vardecls += " syscallFunc"
313 fmt.Printf(srcTemplate, cmdLine(), buildTags(), pack, imp, dynimports, linknames, vardecls, text)
314}
315
316const srcTemplate = `// %s
317// Code generated by the command above; see README.md. DO NOT EDIT.
318
319// +build %s
320
321package %s
322
323import (
324 "syscall"
325 "unsafe"
326)
327%s
328%s
329%s
330var (
331%s
332)
333
334%s
335`