1  
     2  
     3  
     4  
     5  
     6  
     7  
    24  package main
    25  
    26  import (
    27  	"bufio"
    28  	"flag"
    29  	"fmt"
    30  	"os"
    31  	"regexp"
    32  	"strings"
    33  )
    34  
    35  var (
    36  	b32       = flag.Bool("b32", false, "32bit big-endian")
    37  	l32       = flag.Bool("l32", false, "32bit little-endian")
    38  	libc      = flag.Bool("libc", false, "libc system calls")
    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") 
    44  	tags      = flag.String("tags", "", "build tags")
    45  	filename  = flag.String("output", "", "output file name (standard output if omitted)")
    46  
    47  	libcPath = "libc.so"
    48  )
    49  
    50  var (
    51  	regexpComma          = regexp.MustCompile(`\s*,\s*`)
    52  	regexpParamKV        = regexp.MustCompile(`^(\S*) (\S*)$`)
    53  	regexpSys            = regexp.MustCompile(`^\/\/sys\t`)
    54  	regexpSysNonblock    = regexp.MustCompile(`^\/\/sysnb\t`)
    55  	regexpSysDeclaration = regexp.MustCompile(`^\/\/sys(nb)?\t(\w+)\(([^()]*)\)\s*(?:\(([^()]+)\))?\s*(?:=\s*((?i)SYS_[A-Z0-9_]+))?$`)
    56  	regexpPointer        = regexp.MustCompile(`^\*`)
    57  	regexpSlice          = regexp.MustCompile(`^\[](.*)`)
    58  	regexpDragonflyExtp  = regexp.MustCompile(`^(?i)extp(read|write)`)
    59  	regexpSyscallName    = regexp.MustCompile(`([a-z])([A-Z])`)
    60  )
    61  
    62  
    63  func cmdLine() string {
    64  	return "go run mksyscall.go " + strings.Join(os.Args[1:], " ")
    65  }
    66  
    67  
    68  func goBuildTags() string {
    69  	return strings.ReplaceAll(*tags, ",", " && ")
    70  }
    71  
    72  
    73  type Param struct {
    74  	Name string
    75  	Type string
    76  }
    77  
    78  
    79  func usage() {
    80  	fmt.Fprintf(os.Stderr, "usage: go run mksyscall.go [-b32 | -l32] [-tags x,y] [file ...]\n")
    81  	os.Exit(1)
    82  }
    83  
    84  
    85  func parseParamList(list string) []string {
    86  	list = strings.TrimSpace(list)
    87  	if list == "" {
    88  		return []string{}
    89  	}
    90  	return regexpComma.Split(list, -1)
    91  }
    92  
    93  
    94  func parseParam(p string) Param {
    95  	ps := regexpParamKV.FindStringSubmatch(p)
    96  	if ps == nil {
    97  		fmt.Fprintf(os.Stderr, "malformed parameter: %s\n", p)
    98  		os.Exit(1)
    99  	}
   100  	return Param{ps[1], ps[2]}
   101  }
   102  
   103  func main() {
   104  	goos := os.Getenv("GOOS_TARGET")
   105  	if goos == "" {
   106  		goos = os.Getenv("GOOS")
   107  	}
   108  	if goos == "" {
   109  		fmt.Fprintln(os.Stderr, "GOOS not defined in environment")
   110  		os.Exit(1)
   111  	}
   112  
   113  	
   114  	if goos == "linux" {
   115  		if os.Getenv("GOLANG_SYS_BUILD") != "docker" {
   116  			fmt.Fprintf(os.Stderr, "In the Docker-based build system, mksyscall should not be called directly.\n")
   117  			fmt.Fprintf(os.Stderr, "See README.md\n")
   118  			os.Exit(1)
   119  		}
   120  	}
   121  
   122  	flag.Usage = usage
   123  	flag.Parse()
   124  	if len(flag.Args()) <= 0 {
   125  		fmt.Fprintf(os.Stderr, "no files to parse provided\n")
   126  		usage()
   127  	}
   128  
   129  	endianness := ""
   130  	if *b32 {
   131  		endianness = "big-endian"
   132  	} else if *l32 {
   133  		endianness = "little-endian"
   134  	}
   135  
   136  	if goos == "darwin" {
   137  		libcPath = "/usr/lib/libSystem.B.dylib"
   138  		*libc = true
   139  	}
   140  
   141  	trampolines := map[string]bool{}
   142  
   143  	text := ""
   144  	for _, path := range flag.Args() {
   145  		file, err := os.Open(path)
   146  		if err != nil {
   147  			fmt.Fprintf(os.Stderr, err.Error())
   148  			os.Exit(1)
   149  		}
   150  		s := bufio.NewScanner(file)
   151  		for s.Scan() {
   152  			t := s.Text()
   153  			nonblock := regexpSysNonblock.FindStringSubmatch(t)
   154  			if regexpSys.FindStringSubmatch(t) == nil && nonblock == nil {
   155  				continue
   156  			}
   157  
   158  			
   159  			
   160  			
   161  			f := regexpSysDeclaration.FindStringSubmatch(t)
   162  			if f == nil {
   163  				fmt.Fprintf(os.Stderr, "%s:%s\nmalformed //sys declaration\n", path, t)
   164  				os.Exit(1)
   165  			}
   166  			funct, inps, outps, sysname := f[2], f[3], f[4], f[5]
   167  
   168  			
   169  			in := parseParamList(inps)
   170  			out := parseParamList(outps)
   171  
   172  			
   173  			
   174  			
   175  			text += "// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT\n\n"
   176  
   177  			
   178  			outDecl := ""
   179  			if len(out) > 0 {
   180  				outDecl = fmt.Sprintf(" (%s)", strings.Join(out, ", "))
   181  			}
   182  			text += fmt.Sprintf("func %s(%s)%s {\n", funct, strings.Join(in, ", "), outDecl)
   183  
   184  			
   185  			errvar := ""
   186  			for _, param := range out {
   187  				p := parseParam(param)
   188  				if p.Type == "error" {
   189  					errvar = p.Name
   190  					break
   191  				}
   192  			}
   193  
   194  			
   195  			var args []string
   196  			n := 0
   197  			for _, param := range in {
   198  				p := parseParam(param)
   199  				if regexpPointer.FindStringSubmatch(p.Type) != nil {
   200  					args = append(args, "uintptr(unsafe.Pointer("+p.Name+"))")
   201  				} else if p.Type == "string" && errvar != "" {
   202  					text += fmt.Sprintf("\tvar _p%d *byte\n", n)
   203  					text += fmt.Sprintf("\t_p%d, %s = BytePtrFromString(%s)\n", n, errvar, p.Name)
   204  					text += fmt.Sprintf("\tif %s != nil {\n\t\treturn\n\t}\n", errvar)
   205  					args = append(args, fmt.Sprintf("uintptr(unsafe.Pointer(_p%d))", n))
   206  					n++
   207  				} else if p.Type == "string" {
   208  					fmt.Fprintf(os.Stderr, path+":"+funct+" uses string arguments, but has no error return\n")
   209  					text += fmt.Sprintf("\tvar _p%d *byte\n", n)
   210  					text += fmt.Sprintf("\t_p%d, _ = BytePtrFromString(%s)\n", n, p.Name)
   211  					args = append(args, fmt.Sprintf("uintptr(unsafe.Pointer(_p%d))", n))
   212  					n++
   213  				} else if regexpSlice.FindStringSubmatch(p.Type) != nil {
   214  					
   215  					
   216  					
   217  					
   218  					text += fmt.Sprintf("\tvar _p%d unsafe.Pointer\n", n)
   219  					text += fmt.Sprintf("\tif len(%s) > 0 {\n\t\t_p%d = unsafe.Pointer(&%s[0])\n\t}", p.Name, n, p.Name)
   220  					text += fmt.Sprintf(" else {\n\t\t_p%d = unsafe.Pointer(&_zero)\n\t}\n", n)
   221  					args = append(args, fmt.Sprintf("uintptr(_p%d)", n), fmt.Sprintf("uintptr(len(%s))", p.Name))
   222  					n++
   223  				} else if p.Type == "int64" && ((*openbsd && !*libc) || *netbsd) {
   224  					args = append(args, "0")
   225  					if endianness == "big-endian" {
   226  						args = append(args, fmt.Sprintf("uintptr(%s>>32)", p.Name), fmt.Sprintf("uintptr(%s)", p.Name))
   227  					} else if endianness == "little-endian" {
   228  						args = append(args, fmt.Sprintf("uintptr(%s)", p.Name), fmt.Sprintf("uintptr(%s>>32)", p.Name))
   229  					} else {
   230  						args = append(args, fmt.Sprintf("uintptr(%s)", p.Name))
   231  					}
   232  				} else if p.Type == "int64" && *dragonfly {
   233  					if regexpDragonflyExtp.FindStringSubmatch(funct) == nil {
   234  						args = append(args, "0")
   235  					}
   236  					if endianness == "big-endian" {
   237  						args = append(args, fmt.Sprintf("uintptr(%s>>32)", p.Name), fmt.Sprintf("uintptr(%s)", p.Name))
   238  					} else if endianness == "little-endian" {
   239  						args = append(args, fmt.Sprintf("uintptr(%s)", p.Name), fmt.Sprintf("uintptr(%s>>32)", p.Name))
   240  					} else {
   241  						args = append(args, fmt.Sprintf("uintptr(%s)", p.Name))
   242  					}
   243  				} else if (p.Type == "int64" || p.Type == "uint64") && endianness != "" {
   244  					if len(args)%2 == 1 && *arm {
   245  						
   246  						
   247  						args = append(args, "0")
   248  					}
   249  					if endianness == "big-endian" {
   250  						args = append(args, fmt.Sprintf("uintptr(%s>>32)", p.Name), fmt.Sprintf("uintptr(%s)", p.Name))
   251  					} else {
   252  						args = append(args, fmt.Sprintf("uintptr(%s)", p.Name), fmt.Sprintf("uintptr(%s>>32)", p.Name))
   253  					}
   254  				} else {
   255  					args = append(args, fmt.Sprintf("uintptr(%s)", p.Name))
   256  				}
   257  			}
   258  
   259  			
   260  			asm := "Syscall"
   261  			if nonblock != nil {
   262  				if errvar == "" && goos == "linux" {
   263  					asm = "RawSyscallNoError"
   264  				} else {
   265  					asm = "RawSyscall"
   266  				}
   267  			} else {
   268  				if errvar == "" && goos == "linux" {
   269  					asm = "SyscallNoError"
   270  				}
   271  			}
   272  			if len(args) <= 3 {
   273  				for len(args) < 3 {
   274  					args = append(args, "0")
   275  				}
   276  			} else if len(args) <= 6 {
   277  				asm += "6"
   278  				for len(args) < 6 {
   279  					args = append(args, "0")
   280  				}
   281  			} else if len(args) <= 9 {
   282  				asm += "9"
   283  				for len(args) < 9 {
   284  					args = append(args, "0")
   285  				}
   286  			} else {
   287  				fmt.Fprintf(os.Stderr, "%s:%s too many arguments to system call\n", path, funct)
   288  			}
   289  
   290  			
   291  			if sysname == "" {
   292  				sysname = "SYS_" + funct
   293  				sysname = regexpSyscallName.ReplaceAllString(sysname, `${1}_$2`)
   294  				sysname = strings.ToUpper(sysname)
   295  			}
   296  
   297  			var libcFn string
   298  			if *libc {
   299  				asm = "syscall_" + strings.ToLower(asm[:1]) + asm[1:] 
   300  				sysname = strings.TrimPrefix(sysname, "SYS_")         
   301  				sysname = strings.ToLower(sysname)                    
   302  				if *openbsd && *libc {
   303  					switch sysname {
   304  					case "__getcwd":
   305  						sysname = "getcwd"
   306  					case "__sysctl":
   307  						sysname = "sysctl"
   308  					}
   309  				}
   310  				libcFn = sysname
   311  				sysname = "libc_" + sysname + "_trampoline_addr"
   312  			}
   313  
   314  			
   315  			arglist := strings.Join(args, ", ")
   316  			call := fmt.Sprintf("%s(%s, %s)", asm, sysname, arglist)
   317  
   318  			
   319  			body := ""
   320  			ret := []string{"_", "_", "_"}
   321  			doErrno := false
   322  			for i := 0; i < len(out); i++ {
   323  				p := parseParam(out[i])
   324  				reg := ""
   325  				if p.Name == "err" && !*plan9 {
   326  					reg = "e1"
   327  					ret[2] = reg
   328  					doErrno = true
   329  				} else if p.Name == "err" && *plan9 {
   330  					ret[0] = "r0"
   331  					ret[2] = "e1"
   332  					break
   333  				} else {
   334  					reg = fmt.Sprintf("r%d", i)
   335  					ret[i] = reg
   336  				}
   337  				if p.Type == "bool" {
   338  					reg = fmt.Sprintf("%s != 0", reg)
   339  				}
   340  				if p.Type == "int64" && endianness != "" {
   341  					
   342  					if i+2 > len(out) {
   343  						fmt.Fprintf(os.Stderr, "%s:%s not enough registers for int64 return\n", path, funct)
   344  					}
   345  					if endianness == "big-endian" {
   346  						reg = fmt.Sprintf("int64(r%d)<<32 | int64(r%d)", i, i+1)
   347  					} else {
   348  						reg = fmt.Sprintf("int64(r%d)<<32 | int64(r%d)", i+1, i)
   349  					}
   350  					ret[i] = fmt.Sprintf("r%d", i)
   351  					ret[i+1] = fmt.Sprintf("r%d", i+1)
   352  				}
   353  				if reg != "e1" || *plan9 {
   354  					body += fmt.Sprintf("\t%s = %s(%s)\n", p.Name, p.Type, reg)
   355  				}
   356  			}
   357  			if ret[0] == "_" && ret[1] == "_" && ret[2] == "_" {
   358  				text += fmt.Sprintf("\t%s\n", call)
   359  			} else {
   360  				if errvar == "" && goos == "linux" {
   361  					
   362  					text += fmt.Sprintf("\t%s, %s := %s\n", ret[0], ret[1], call)
   363  				} else {
   364  					text += fmt.Sprintf("\t%s, %s, %s := %s\n", ret[0], ret[1], ret[2], call)
   365  				}
   366  			}
   367  			text += body
   368  
   369  			if *plan9 && ret[2] == "e1" {
   370  				text += "\tif int32(r0) == -1 {\n"
   371  				text += "\t\terr = e1\n"
   372  				text += "\t}\n"
   373  			} else if doErrno {
   374  				text += "\tif e1 != 0 {\n"
   375  				text += "\t\terr = errnoErr(e1)\n"
   376  				text += "\t}\n"
   377  			}
   378  			text += "\treturn\n"
   379  			text += "}\n\n"
   380  
   381  			if *libc && !trampolines[libcFn] {
   382  				
   383  				trampolines[libcFn] = true
   384  				
   385  				text += fmt.Sprintf("var libc_%s_trampoline_addr uintptr\n\n", libcFn)
   386  				
   387  				
   388  				text += fmt.Sprintf("//go:cgo_import_dynamic libc_%s %s %q\n", libcFn, libcFn, libcPath)
   389  				text += "\n"
   390  			}
   391  		}
   392  		if err := s.Err(); err != nil {
   393  			fmt.Fprintf(os.Stderr, err.Error())
   394  			os.Exit(1)
   395  		}
   396  		file.Close()
   397  	}
   398  	fmt.Printf(srcTemplate, cmdLine(), goBuildTags(), text)
   399  }
   400  
   401  const srcTemplate = `// %s
   402  // Code generated by the command above; see README.md. DO NOT EDIT.
   403  
   404  //go:build %s
   405  
   406  package unix
   407  
   408  import (
   409  	"syscall"
   410  	"unsafe"
   411  )
   412  
   413  var _ syscall.Errno
   414  
   415  %s
   416  `
   417  
View as plain text