1#!/usr/bin/env perl 2# Copyright 2009 The Go Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style 4# license that can be found in the LICENSE file. 5 6# This program reads a file containing function prototypes 7# (like syscall_darwin.go) and generates system call bodies. 8# The prototypes are marked by lines beginning with "//sys" 9# and read like func declarations if //sys is replaced by func, but: 10# * The parameter lists must give a name for each argument. 11# This includes return parameters. 12# * The parameter lists must give a type for each argument: 13# the (x, y, z int) shorthand is not allowed. 14# * If the return parameter is an error number, it must be named errno. 15 16# A line beginning with //sysnb is like //sys, except that the 17# goroutine will not be suspended during the execution of the system 18# call. This must only be used for system calls which can never 19# block, as otherwise the system call could cause all goroutines to 20# hang. 21 22use strict; 23 24my $cmdline = "mksyscall.pl " . join(' ', @ARGV); 25my $errors = 0; 26my $_32bit = ""; 27my $plan9 = 0; 28my $openbsd = 0; 29my $netbsd = 0; 30my $dragonfly = 0; 31my $arm = 0; # 64-bit value should use (even, odd)-pair 32my $tags = ""; # build tags 33 34if($ARGV[0] eq "-b32") { 35 $_32bit = "big-endian"; 36 shift; 37} elsif($ARGV[0] eq "-l32") { 38 $_32bit = "little-endian"; 39 shift; 40} 41if($ARGV[0] eq "-plan9") { 42 $plan9 = 1; 43 shift; 44} 45if($ARGV[0] eq "-openbsd") { 46 $openbsd = 1; 47 shift; 48} 49if($ARGV[0] eq "-netbsd") { 50 $netbsd = 1; 51 shift; 52} 53if($ARGV[0] eq "-dragonfly") { 54 $dragonfly = 1; 55 shift; 56} 57if($ARGV[0] eq "-arm") { 58 $arm = 1; 59 shift; 60} 61if($ARGV[0] eq "-tags") { 62 shift; 63 $tags = $ARGV[0]; 64 shift; 65} 66 67if($ARGV[0] =~ /^-/) { 68 print STDERR "usage: mksyscall.pl [-b32 | -l32] [-tags x,y] [file ...]\n"; 69 exit 1; 70} 71 72# Check that we are using the new build system if we should 73if($ENV{'GOOS'} eq "linux" && $ENV{'GOARCH'} ne "sparc64") { 74 if($ENV{'GOLANG_SYS_BUILD'} ne "docker") { 75 print STDERR "In the new build system, mksyscall should not be called directly.\n"; 76 print STDERR "See README.md\n"; 77 exit 1; 78 } 79} 80 81 82sub parseparamlist($) { 83 my ($list) = @_; 84 $list =~ s/^\s*//; 85 $list =~ s/\s*$//; 86 if($list eq "") { 87 return (); 88 } 89 return split(/\s*,\s*/, $list); 90} 91 92sub parseparam($) { 93 my ($p) = @_; 94 if($p !~ /^(\S*) (\S*)$/) { 95 print STDERR "$ARGV:$.: malformed parameter: $p\n"; 96 $errors = 1; 97 return ("xx", "int"); 98 } 99 return ($1, $2); 100} 101 102my $text = ""; 103while(<>) { 104 chomp; 105 s/\s+/ /g; 106 s/^\s+//; 107 s/\s+$//; 108 my $nonblock = /^\/\/sysnb /; 109 next if !/^\/\/sys / && !$nonblock; 110 111 # Line must be of the form 112 # func Open(path string, mode int, perm int) (fd int, errno error) 113 # Split into name, in params, out params. 114 if(!/^\/\/sys(nb)? (\w+)\(([^()]*)\)\s*(?:\(([^()]+)\))?\s*(?:=\s*((?i)SYS_[A-Z0-9_]+))?$/) { 115 print STDERR "$ARGV:$.: malformed //sys declaration\n"; 116 $errors = 1; 117 next; 118 } 119 my ($func, $in, $out, $sysname) = ($2, $3, $4, $5); 120 121 # Split argument lists on comma. 122 my @in = parseparamlist($in); 123 my @out = parseparamlist($out); 124 125 # Try in vain to keep people from editing this file. 126 # The theory is that they jump into the middle of the file 127 # without reading the header. 128 $text .= "// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT\n\n"; 129 130 # Go function header. 131 my $out_decl = @out ? sprintf(" (%s)", join(', ', @out)) : ""; 132 $text .= sprintf "func %s(%s)%s {\n", $func, join(', ', @in), $out_decl; 133 134 # Check if err return available 135 my $errvar = ""; 136 foreach my $p (@out) { 137 my ($name, $type) = parseparam($p); 138 if($type eq "error") { 139 $errvar = $name; 140 last; 141 } 142 } 143 144 # Prepare arguments to Syscall. 145 my @args = (); 146 my $n = 0; 147 foreach my $p (@in) { 148 my ($name, $type) = parseparam($p); 149 if($type =~ /^\*/) { 150 push @args, "uintptr(unsafe.Pointer($name))"; 151 } elsif($type eq "string" && $errvar ne "") { 152 $text .= "\tvar _p$n *byte\n"; 153 $text .= "\t_p$n, $errvar = BytePtrFromString($name)\n"; 154 $text .= "\tif $errvar != nil {\n\t\treturn\n\t}\n"; 155 push @args, "uintptr(unsafe.Pointer(_p$n))"; 156 $n++; 157 } elsif($type eq "string") { 158 print STDERR "$ARGV:$.: $func uses string arguments, but has no error return\n"; 159 $text .= "\tvar _p$n *byte\n"; 160 $text .= "\t_p$n, _ = BytePtrFromString($name)\n"; 161 push @args, "uintptr(unsafe.Pointer(_p$n))"; 162 $n++; 163 } elsif($type =~ /^\[\](.*)/) { 164 # Convert slice into pointer, length. 165 # Have to be careful not to take address of &a[0] if len == 0: 166 # pass dummy pointer in that case. 167 # Used to pass nil, but some OSes or simulators reject write(fd, nil, 0). 168 $text .= "\tvar _p$n unsafe.Pointer\n"; 169 $text .= "\tif len($name) > 0 {\n\t\t_p$n = unsafe.Pointer(\&${name}[0])\n\t}"; 170 $text .= " else {\n\t\t_p$n = unsafe.Pointer(&_zero)\n\t}"; 171 $text .= "\n"; 172 push @args, "uintptr(_p$n)", "uintptr(len($name))"; 173 $n++; 174 } elsif($type eq "int64" && ($openbsd || $netbsd)) { 175 push @args, "0"; 176 if($_32bit eq "big-endian") { 177 push @args, "uintptr($name>>32)", "uintptr($name)"; 178 } elsif($_32bit eq "little-endian") { 179 push @args, "uintptr($name)", "uintptr($name>>32)"; 180 } else { 181 push @args, "uintptr($name)"; 182 } 183 } elsif($type eq "int64" && $dragonfly) { 184 if ($func !~ /^extp(read|write)/i) { 185 push @args, "0"; 186 } 187 if($_32bit eq "big-endian") { 188 push @args, "uintptr($name>>32)", "uintptr($name)"; 189 } elsif($_32bit eq "little-endian") { 190 push @args, "uintptr($name)", "uintptr($name>>32)"; 191 } else { 192 push @args, "uintptr($name)"; 193 } 194 } elsif($type eq "int64" && $_32bit ne "") { 195 if(@args % 2 && $arm) { 196 # arm abi specifies 64-bit argument uses 197 # (even, odd) pair 198 push @args, "0" 199 } 200 if($_32bit eq "big-endian") { 201 push @args, "uintptr($name>>32)", "uintptr($name)"; 202 } else { 203 push @args, "uintptr($name)", "uintptr($name>>32)"; 204 } 205 } else { 206 push @args, "uintptr($name)"; 207 } 208 } 209 210 # Determine which form to use; pad args with zeros. 211 my $asm = "Syscall"; 212 if ($nonblock) { 213 if ($errvar eq "" && $ENV{'GOOS'} eq "linux") { 214 $asm = "RawSyscallNoError"; 215 } else { 216 $asm = "RawSyscall"; 217 } 218 } else { 219 if ($errvar eq "" && $ENV{'GOOS'} eq "linux") { 220 $asm = "SyscallNoError"; 221 } 222 } 223 if(@args <= 3) { 224 while(@args < 3) { 225 push @args, "0"; 226 } 227 } elsif(@args <= 6) { 228 $asm .= "6"; 229 while(@args < 6) { 230 push @args, "0"; 231 } 232 } elsif(@args <= 9) { 233 $asm .= "9"; 234 while(@args < 9) { 235 push @args, "0"; 236 } 237 } else { 238 print STDERR "$ARGV:$.: too many arguments to system call\n"; 239 } 240 241 # System call number. 242 if($sysname eq "") { 243 $sysname = "SYS_$func"; 244 $sysname =~ s/([a-z])([A-Z])/${1}_$2/g; # turn FooBar into Foo_Bar 245 $sysname =~ y/a-z/A-Z/; 246 } 247 248 # Actual call. 249 my $args = join(', ', @args); 250 my $call = "$asm($sysname, $args)"; 251 252 # Assign return values. 253 my $body = ""; 254 my @ret = ("_", "_", "_"); 255 my $do_errno = 0; 256 for(my $i=0; $i<@out; $i++) { 257 my $p = $out[$i]; 258 my ($name, $type) = parseparam($p); 259 my $reg = ""; 260 if($name eq "err" && !$plan9) { 261 $reg = "e1"; 262 $ret[2] = $reg; 263 $do_errno = 1; 264 } elsif($name eq "err" && $plan9) { 265 $ret[0] = "r0"; 266 $ret[2] = "e1"; 267 next; 268 } else { 269 $reg = sprintf("r%d", $i); 270 $ret[$i] = $reg; 271 } 272 if($type eq "bool") { 273 $reg = "$reg != 0"; 274 } 275 if($type eq "int64" && $_32bit ne "") { 276 # 64-bit number in r1:r0 or r0:r1. 277 if($i+2 > @out) { 278 print STDERR "$ARGV:$.: not enough registers for int64 return\n"; 279 } 280 if($_32bit eq "big-endian") { 281 $reg = sprintf("int64(r%d)<<32 | int64(r%d)", $i, $i+1); 282 } else { 283 $reg = sprintf("int64(r%d)<<32 | int64(r%d)", $i+1, $i); 284 } 285 $ret[$i] = sprintf("r%d", $i); 286 $ret[$i+1] = sprintf("r%d", $i+1); 287 } 288 if($reg ne "e1" || $plan9) { 289 $body .= "\t$name = $type($reg)\n"; 290 } 291 } 292 if ($ret[0] eq "_" && $ret[1] eq "_" && $ret[2] eq "_") { 293 $text .= "\t$call\n"; 294 } else { 295 if ($errvar eq "" && $ENV{'GOOS'} eq "linux") { 296 # raw syscall without error on Linux, see golang.org/issue/22924 297 $text .= "\t$ret[0], $ret[1] := $call\n"; 298 } else { 299 $text .= "\t$ret[0], $ret[1], $ret[2] := $call\n"; 300 } 301 } 302 $text .= $body; 303 304 if ($plan9 && $ret[2] eq "e1") { 305 $text .= "\tif int32(r0) == -1 {\n"; 306 $text .= "\t\terr = e1\n"; 307 $text .= "\t}\n"; 308 } elsif ($do_errno) { 309 $text .= "\tif e1 != 0 {\n"; 310 $text .= "\t\terr = errnoErr(e1)\n"; 311 $text .= "\t}\n"; 312 } 313 $text .= "\treturn\n"; 314 $text .= "}\n\n"; 315} 316 317chomp $text; 318chomp $text; 319 320if($errors) { 321 exit 1; 322} 323 324print <<EOF; 325// $cmdline 326// Code generated by the command above; see README.md. DO NOT EDIT. 327 328// +build $tags 329 330package unix 331 332import ( 333 "syscall" 334 "unsafe" 335) 336 337var _ syscall.Errno 338 339$text 340EOF 341exit 0; 342