Home | History | Annotate | Download | only in unix
      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_solaris.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 err.
     15 #	* If go func name needs to be different than its libc name,
     16 #	* or the function is not in libc, name could be specified
     17 #	* at the end, after "=" sign, like
     18 #	  //sys getsockopt(s int, level int, name int, val uintptr, vallen *_Socklen) (err error) = libsocket.getsockopt
     19 
     20 use strict;
     21 
     22 my $cmdline = "mksyscall_solaris.pl " . join(' ', @ARGV);
     23 my $errors = 0;
     24 my $_32bit = "";
     25 my $tags = "";  # build tags
     26 
     27 binmode STDOUT;
     28 
     29 if($ARGV[0] eq "-b32") {
     30 	$_32bit = "big-endian";
     31 	shift;
     32 } elsif($ARGV[0] eq "-l32") {
     33 	$_32bit = "little-endian";
     34 	shift;
     35 }
     36 if($ARGV[0] eq "-tags") {
     37 	shift;
     38 	$tags = $ARGV[0];
     39 	shift;
     40 }
     41 
     42 if($ARGV[0] =~ /^-/) {
     43 	print STDERR "usage: mksyscall_solaris.pl [-b32 | -l32] [-tags x,y] [file ...]\n";
     44 	exit 1;
     45 }
     46 
     47 sub parseparamlist($) {
     48 	my ($list) = @_;
     49 	$list =~ s/^\s*//;
     50 	$list =~ s/\s*$//;
     51 	if($list eq "") {
     52 		return ();
     53 	}
     54 	return split(/\s*,\s*/, $list);
     55 }
     56 
     57 sub parseparam($) {
     58 	my ($p) = @_;
     59 	if($p !~ /^(\S*) (\S*)$/) {
     60 		print STDERR "$ARGV:$.: malformed parameter: $p\n";
     61 		$errors = 1;
     62 		return ("xx", "int");
     63 	}
     64 	return ($1, $2);
     65 }
     66 
     67 my $package = "";
     68 my $text = "";
     69 my $dynimports = "";
     70 my $linknames = "";
     71 my @vars = ();
     72 while(<>) {
     73 	chomp;
     74 	s/\s+/ /g;
     75 	s/^\s+//;
     76 	s/\s+$//;
     77 	$package = $1 if !$package && /^package (\S+)$/;
     78 	my $nonblock = /^\/\/sysnb /;
     79 	next if !/^\/\/sys / && !$nonblock;
     80 
     81 	# Line must be of the form
     82 	#	func Open(path string, mode int, perm int) (fd int, err error)
     83 	# Split into name, in params, out params.
     84 	if(!/^\/\/sys(nb)? (\w+)\(([^()]*)\)\s*(?:\(([^()]+)\))?\s*(?:=\s*(?:(\w*)\.)?(\w*))?$/) {
     85 		print STDERR "$ARGV:$.: malformed //sys declaration\n";
     86 		$errors = 1;
     87 		next;
     88 	}
     89 	my ($nb, $func, $in, $out, $modname, $sysname) = ($1, $2, $3, $4, $5, $6);
     90 
     91 	# Split argument lists on comma.
     92 	my @in = parseparamlist($in);
     93 	my @out = parseparamlist($out);
     94 
     95 	# So file name.
     96 	if($modname eq "") {
     97 		$modname = "libc";
     98 	}
     99 
    100 	# System call name.
    101 	if($sysname eq "") {
    102 		$sysname = "$func";
    103 	}
    104 
    105 	# System call pointer variable name.
    106 	my $sysvarname = "proc$sysname";
    107 
    108 	my $strconvfunc = "BytePtrFromString";
    109 	my $strconvtype = "*byte";
    110 
    111 	$sysname =~ y/A-Z/a-z/; # All libc functions are lowercase.
    112 
    113 	# Runtime import of function to allow cross-platform builds.
    114 	$dynimports .= "//go:cgo_import_dynamic libc_${sysname} ${sysname} \"$modname.so\"\n";
    115 	# Link symbol to proc address variable.
    116 	$linknames .= "//go:linkname ${sysvarname} libc_${sysname}\n";
    117 	# Library proc address variable.
    118 	push @vars, $sysvarname;
    119 
    120 	# Go function header.
    121 	$out = join(', ', @out);
    122 	if($out ne "") {
    123 		$out = " ($out)";
    124 	}
    125 	if($text ne "") {
    126 		$text .= "\n"
    127 	}
    128 	$text .= sprintf "func %s(%s)%s {\n", $func, join(', ', @in), $out;
    129 
    130 	# Check if err return available
    131 	my $errvar = "";
    132 	foreach my $p (@out) {
    133 		my ($name, $type) = parseparam($p);
    134 		if($type eq "error") {
    135 			$errvar = $name;
    136 			last;
    137 		}
    138 	}
    139 
    140 	# Prepare arguments to Syscall.
    141 	my @args = ();
    142 	my $n = 0;
    143 	foreach my $p (@in) {
    144 		my ($name, $type) = parseparam($p);
    145 		if($type =~ /^\*/) {
    146 			push @args, "uintptr(unsafe.Pointer($name))";
    147 		} elsif($type eq "string" && $errvar ne "") {
    148 			$text .= "\tvar _p$n $strconvtype\n";
    149 			$text .= "\t_p$n, $errvar = $strconvfunc($name)\n";
    150 			$text .= "\tif $errvar != nil {\n\t\treturn\n\t}\n";
    151 			push @args, "uintptr(unsafe.Pointer(_p$n))";
    152 			$n++;
    153 		} elsif($type eq "string") {
    154 			print STDERR "$ARGV:$.: $func uses string arguments, but has no error return\n";
    155 			$text .= "\tvar _p$n $strconvtype\n";
    156 			$text .= "\t_p$n, _ = $strconvfunc($name)\n";
    157 			push @args, "uintptr(unsafe.Pointer(_p$n))";
    158 			$n++;
    159 		} elsif($type =~ /^\[\](.*)/) {
    160 			# Convert slice into pointer, length.
    161 			# Have to be careful not to take address of &a[0] if len == 0:
    162 			# pass nil in that case.
    163 			$text .= "\tvar _p$n *$1\n";
    164 			$text .= "\tif len($name) > 0 {\n\t\t_p$n = \&$name\[0]\n\t}\n";
    165 			push @args, "uintptr(unsafe.Pointer(_p$n))", "uintptr(len($name))";
    166 			$n++;
    167 		} elsif($type eq "int64" && $_32bit ne "") {
    168 			if($_32bit eq "big-endian") {
    169 				push @args, "uintptr($name >> 32)", "uintptr($name)";
    170 			} else {
    171 				push @args, "uintptr($name)", "uintptr($name >> 32)";
    172 			}
    173 		} elsif($type eq "bool") {
    174  			$text .= "\tvar _p$n uint32\n";
    175 			$text .= "\tif $name {\n\t\t_p$n = 1\n\t} else {\n\t\t_p$n = 0\n\t}\n";
    176 			push @args, "uintptr(_p$n)";
    177 			$n++;
    178 		} else {
    179 			push @args, "uintptr($name)";
    180 		}
    181 	}
    182 	my $nargs = @args;
    183 
    184 	# Determine which form to use; pad args with zeros.
    185 	my $asm = "sysvicall6";
    186 	if ($nonblock) {
    187 		$asm = "rawSysvicall6";
    188 	}
    189 	if(@args <= 6) {
    190 		while(@args < 6) {
    191 			push @args, "0";
    192 		}
    193 	} else {
    194 		print STDERR "$ARGV:$.: too many arguments to system call\n";
    195 	}
    196 
    197 	# Actual call.
    198 	my $args = join(', ', @args);
    199 	my $call = "$asm(uintptr(unsafe.Pointer(&$sysvarname)), $nargs, $args)";
    200 
    201 	# Assign return values.
    202 	my $body = "";
    203 	my $failexpr = "";
    204 	my @ret = ("_", "_", "_");
    205 	my @pout= ();
    206 	my $do_errno = 0;
    207 	for(my $i=0; $i<@out; $i++) {
    208 		my $p = $out[$i];
    209 		my ($name, $type) = parseparam($p);
    210 		my $reg = "";
    211 		if($name eq "err") {
    212 			$reg = "e1";
    213 			$ret[2] = $reg;
    214 			$do_errno = 1;
    215 		} else {
    216 			$reg = sprintf("r%d", $i);
    217 			$ret[$i] = $reg;
    218 		}
    219 		if($type eq "bool") {
    220 			$reg = "$reg != 0";
    221 		}
    222 		if($type eq "int64" && $_32bit ne "") {
    223 			# 64-bit number in r1:r0 or r0:r1.
    224 			if($i+2 > @out) {
    225 				print STDERR "$ARGV:$.: not enough registers for int64 return\n";
    226 			}
    227 			if($_32bit eq "big-endian") {
    228 				$reg = sprintf("int64(r%d)<<32 | int64(r%d)", $i, $i+1);
    229 			} else {
    230 				$reg = sprintf("int64(r%d)<<32 | int64(r%d)", $i+1, $i);
    231 			}
    232 			$ret[$i] = sprintf("r%d", $i);
    233 			$ret[$i+1] = sprintf("r%d", $i+1);
    234 		}
    235 		if($reg ne "e1") {
    236 			$body .= "\t$name = $type($reg)\n";
    237 		}
    238 	}
    239 	if ($ret[0] eq "_" && $ret[1] eq "_" && $ret[2] eq "_") {
    240 		$text .= "\t$call\n";
    241 	} else {
    242 		$text .= "\t$ret[0], $ret[1], $ret[2] := $call\n";
    243 	}
    244 	$text .= $body;
    245 
    246 	if ($do_errno) {
    247 		$text .= "\tif e1 != 0 {\n";
    248 		$text .= "\t\terr = e1\n";
    249 		$text .= "\t}\n";
    250 	}
    251 	$text .= "\treturn\n";
    252 	$text .= "}\n";
    253 }
    254 
    255 if($errors) {
    256 	exit 1;
    257 }
    258 
    259 print <<EOF;
    260 // $cmdline
    261 // Code generated by the command above; see README.md. DO NOT EDIT.
    262 
    263 // +build $tags
    264 
    265 package $package
    266 
    267 import (
    268 	"syscall"
    269 	"unsafe"
    270 )
    271 EOF
    272 
    273 print "import \"golang.org/x/sys/unix\"\n" if $package ne "unix";
    274 
    275 my $vardecls = "\t" . join(",\n\t", @vars);
    276 $vardecls .= " syscallFunc";
    277 
    278 chomp($_=<<EOF);
    279 
    280 $dynimports
    281 $linknames
    282 var (
    283 $vardecls
    284 )
    285 
    286 $text
    287 EOF
    288 print $_;
    289 exit 0;
    290