1 // Copyright 2013 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.
6 mkwinsyscall generates windows system call bodies
8 It parses all files specified on command line containing function
9 prototypes (like syscall_windows.go) and prints system call bodies
12 The prototypes are marked by lines beginning with "//sys" and read
13 like func declarations if //sys is replaced by func, but:
15 * The parameter lists must give a name for each argument. This
16 includes return parameters.
18 * The parameter lists must give a type for each argument:
19 the (x, y, z int) shorthand is not allowed.
21 * If the return parameter is an error number, it must be named err.
23 * If go func name needs to be different from its winapi dll name,
24 the winapi name could be specified at the end, after "=" sign, like
25 //sys LoadLibrary(libname string) (handle uint32, err error) = LoadLibraryA
27 * Each function that returns err needs to supply a condition, that
28 return value of winapi will be tested against to detect failure.
29 This would set err to windows "last-error", otherwise it will be nil.
30 The value can be provided at end of //sys declaration, like
31 //sys LoadLibrary(libname string) (handle uint32, err error) [failretval==-1] = LoadLibraryA
32 and is [failretval==0] by default.
34 * If the function name ends in a "?", then the function not existing is non-
35 fatal, and an error will be returned instead of panicking.
38 mkwinsyscall [flags] [path ...]
42 Specify output file name (outputs to console if blank).
44 Generate print statement after every syscall.
70 filename = flag.String("output", "", "output file name (standard output if omitted)")
71 printTraceFlag = flag.Bool("trace", false, "generate print statement after every syscall")
72 systemDLL = flag.Bool("systemdll", true, "whether all DLLs should be loaded from the Windows system directory")
75 func trim(s string) string {
76 return strings.Trim(s, " \t")
79 var packageName string
81 func packagename() string {
85 func syscalldot() string {
86 if packageName == "syscall" {
92 // Param is function parameter
100 // tmpVar returns temp variable name that will be used to represent p during syscall.
101 func (p *Param) tmpVar() string {
103 p.tmpVarIdx = p.fn.curTmpVarIdx
106 return fmt.Sprintf("_p%d", p.tmpVarIdx)
109 // BoolTmpVarCode returns source code for bool temp variable.
110 func (p *Param) BoolTmpVarCode() string {
111 const code = `var %[1]s uint32
115 return fmt.Sprintf(code, p.tmpVar(), p.Name)
118 // BoolPointerTmpVarCode returns source code for bool temp variable.
119 func (p *Param) BoolPointerTmpVarCode() string {
120 const code = `var %[1]s uint32
124 return fmt.Sprintf(code, p.tmpVar(), p.Name)
127 // SliceTmpVarCode returns source code for slice temp variable.
128 func (p *Param) SliceTmpVarCode() string {
129 const code = `var %s *%s
134 return fmt.Sprintf(code, tmp, p.Type[2:], p.Name, tmp, p.Name)
137 // StringTmpVarCode returns source code for string temp variable.
138 func (p *Param) StringTmpVarCode() string {
139 errvar := p.fn.Rets.ErrorVarName()
144 const code = `var %s %s
146 s := fmt.Sprintf(code, tmp, p.fn.StrconvType(), tmp, errvar, p.fn.StrconvFunc(), p.Name)
154 return s + fmt.Sprintf(morecode, errvar)
157 // TmpVarCode returns source code for temp variable.
158 func (p *Param) TmpVarCode() string {
160 case p.Type == "bool":
161 return p.BoolTmpVarCode()
162 case p.Type == "*bool":
163 return p.BoolPointerTmpVarCode()
164 case strings.HasPrefix(p.Type, "[]"):
165 return p.SliceTmpVarCode()
171 // TmpVarReadbackCode returns source code for reading back the temp variable into the original variable.
172 func (p *Param) TmpVarReadbackCode() string {
174 case p.Type == "*bool":
175 return fmt.Sprintf("*%s = %s != 0", p.Name, p.tmpVar())
181 // TmpVarHelperCode returns source code for helper's temp variable.
182 func (p *Param) TmpVarHelperCode() string {
183 if p.Type != "string" {
186 return p.StringTmpVarCode()
189 // SyscallArgList returns source code fragments representing p parameter
190 // in syscall. Slices are translated into 2 syscall parameters: pointer to
191 // the first element and length.
192 func (p *Param) SyscallArgList() []string {
197 s = fmt.Sprintf("unsafe.Pointer(&%s)", p.tmpVar())
199 s = fmt.Sprintf("unsafe.Pointer(%s)", p.Name)
202 case strings.HasPrefix(t, "[]"):
204 fmt.Sprintf("uintptr(unsafe.Pointer(%s))", p.tmpVar()),
205 fmt.Sprintf("uintptr(len(%s))", p.Name),
210 return []string{fmt.Sprintf("uintptr(%s)", s)}
213 // IsError determines if p parameter is used to return error.
214 func (p *Param) IsError() bool {
215 return p.Name == "err" && p.Type == "error"
218 // HelperType returns type of parameter p used in helper function.
219 func (p *Param) HelperType() string {
220 if p.Type == "string" {
221 return p.fn.StrconvType()
226 // join concatenates parameters ps into a string with sep separator.
227 // Each parameter is converted into string by applying fn to it
228 // before conversion.
229 func join(ps []*Param, fn func(*Param) string, sep string) string {
233 a := make([]string, 0)
234 for _, p := range ps {
237 return strings.Join(a, sep)
240 // Rets describes function return parameters.
249 // ErrorVarName returns error variable name for r.
250 func (r *Rets) ErrorVarName() string {
254 if r.Type == "error" {
260 // ToParams converts r into slice of *Param.
261 func (r *Rets) ToParams() []*Param {
262 ps := make([]*Param, 0)
264 ps = append(ps, &Param{Name: r.Name, Type: r.Type})
267 ps = append(ps, &Param{Name: "err", Type: "error"})
272 // List returns source code of syscall return parameters.
273 func (r *Rets) List() string {
274 s := join(r.ToParams(), func(p *Param) string { return p.Name + " " + p.Type }, ", ")
277 } else if r.fnMaybeAbsent {
283 // PrintList returns source code of trace printing part correspondent
284 // to syscall return values.
285 func (r *Rets) PrintList() string {
286 return join(r.ToParams(), func(p *Param) string { return fmt.Sprintf(`"%s=", %s, `, p.Name, p.Name) }, `", ", `)
289 // SetReturnValuesCode returns source code that accepts syscall return values.
290 func (r *Rets) SetReturnValuesCode() string {
291 if r.Name == "" && !r.ReturnsError {
302 return fmt.Sprintf("%s, _, %s := ", retvar, errvar)
305 func (r *Rets) useLongHandleErrorCode(retvar string) string {
306 const code = `if %s {
309 cond := retvar + " == 0"
310 if r.FailCond != "" {
311 cond = strings.Replace(r.FailCond, "failretval", retvar, 1)
313 return fmt.Sprintf(code, cond)
316 // SetErrorCode returns source code that sets return parameters.
317 func (r *Rets) SetErrorCode() string {
318 const code = `if r0 != 0 {
321 if r.Name == "" && !r.ReturnsError {
325 return r.useLongHandleErrorCode("r1")
327 if r.Type == "error" {
328 return fmt.Sprintf(code, r.Name, syscalldot())
332 case r.Type[0] == '*':
333 s = fmt.Sprintf("%s = (%s)(unsafe.Pointer(r0))", r.Name, r.Type)
334 case r.Type == "bool":
335 s = fmt.Sprintf("%s = r0 != 0", r.Name)
337 s = fmt.Sprintf("%s = %s(r0)", r.Name, r.Type)
342 return s + "\n\t" + r.useLongHandleErrorCode(r.Name)
345 // Fn describes syscall function.
354 // TODO: get rid of this field and just use parameter index instead
355 curTmpVarIdx int // insure tmp variables have uniq names
358 // extractParams parses s to extract function parameters.
359 func extractParams(s string, f *Fn) ([]*Param, error) {
364 a := strings.Split(s, ",")
365 ps := make([]*Param, len(a))
368 b := strings.Split(s2, " ")
370 b = strings.Split(s2, "\t")
372 return nil, errors.New("Could not extract function parameter from \"" + s2 + "\"")
385 // extractSection extracts text out of string s starting after start
386 // and ending just before end. found return value will indicate success,
387 // and prefix, body and suffix will contain correspondent parts of string s.
388 func extractSection(s string, start, end rune) (prefix, body, suffix string, found bool) {
390 if strings.HasPrefix(s, string(start)) {
394 a := strings.SplitN(s, string(start), 2)
396 return "", "", s, false
401 a := strings.SplitN(body, string(end), 2)
403 return "", "", "", false
405 return prefix, a[0], a[1], true
408 // newFn parses string s and return created function Fn.
409 func newFn(s string) (*Fn, error) {
414 PrintTrace: *printTraceFlag,
416 // function name and args
417 prefix, body, s, found := extractSection(s, '(', ')')
418 if !found || prefix == "" {
419 return nil, errors.New("Could not extract function name and parameters from \"" + f.src + "\"")
423 f.Params, err = extractParams(body, f)
428 _, body, s, found = extractSection(s, '(', ')')
430 r, err := extractParams(body, f)
438 f.Rets.ReturnsError = true
440 f.Rets.Name = r[0].Name
441 f.Rets.Type = r[0].Type
445 return nil, errors.New("Only last windows error is allowed as second return value in \"" + f.src + "\"")
447 f.Rets.ReturnsError = true
448 f.Rets.Name = r[0].Name
449 f.Rets.Type = r[0].Type
451 return nil, errors.New("Too many return values in \"" + f.src + "\"")
455 _, body, s, found = extractSection(s, '[', ']')
457 f.Rets.FailCond = body
459 // dll and dll function names
464 if !strings.HasPrefix(s, "=") {
465 return nil, errors.New("Could not extract dll name from \"" + f.src + "\"")
468 a := strings.Split(s, ".")
476 return nil, errors.New("Could not extract dll name from \"" + f.src + "\"")
478 if n := f.dllfuncname; strings.HasSuffix(n, "?") {
479 f.dllfuncname = n[:len(n)-1]
480 f.Rets.fnMaybeAbsent = true
485 // DLLName returns DLL name for function f.
486 func (f *Fn) DLLName() string {
493 // DLLName returns DLL function name for function f.
494 func (f *Fn) DLLFuncName() string {
495 if f.dllfuncname == "" {
501 // ParamList returns source code for function f parameters.
502 func (f *Fn) ParamList() string {
503 return join(f.Params, func(p *Param) string { return p.Name + " " + p.Type }, ", ")
506 // HelperParamList returns source code for helper function f parameters.
507 func (f *Fn) HelperParamList() string {
508 return join(f.Params, func(p *Param) string { return p.Name + " " + p.HelperType() }, ", ")
511 // ParamPrintList returns source code of trace printing part correspondent
512 // to syscall input parameters.
513 func (f *Fn) ParamPrintList() string {
514 return join(f.Params, func(p *Param) string { return fmt.Sprintf(`"%s=", %s, `, p.Name, p.Name) }, `", ", `)
517 // ParamCount return number of syscall parameters for function f.
518 func (f *Fn) ParamCount() int {
520 for _, p := range f.Params {
521 n += len(p.SyscallArgList())
526 // SyscallParamCount determines which version of Syscall/Syscall6/Syscall9/...
527 // to use. It returns parameter count for correspondent SyscallX function.
528 func (f *Fn) SyscallParamCount() int {
542 panic("too many arguments to system call")
546 // Syscall determines which SyscallX function to use for function f.
547 func (f *Fn) Syscall() string {
548 c := f.SyscallParamCount()
550 return syscalldot() + "Syscall"
552 return syscalldot() + "Syscall" + strconv.Itoa(c)
555 // SyscallParamList returns source code for SyscallX parameters for function f.
556 func (f *Fn) SyscallParamList() string {
557 a := make([]string, 0)
558 for _, p := range f.Params {
559 a = append(a, p.SyscallArgList()...)
561 for len(a) < f.SyscallParamCount() {
564 return strings.Join(a, ", ")
567 // HelperCallParamList returns source code of call into function f helper.
568 func (f *Fn) HelperCallParamList() string {
569 a := make([]string, 0, len(f.Params))
570 for _, p := range f.Params {
572 if p.Type == "string" {
577 return strings.Join(a, ", ")
580 // MaybeAbsent returns source code for handling functions that are possibly unavailable.
581 func (p *Fn) MaybeAbsent() string {
582 if !p.Rets.fnMaybeAbsent {
585 const code = `%[1]s = proc%[2]s.Find()
589 errorVar := p.Rets.ErrorVarName()
593 return fmt.Sprintf(code, errorVar, p.DLLFuncName())
596 // IsUTF16 is true, if f is W (utf16) function. It is false
597 // for all A (ascii) functions.
598 func (f *Fn) IsUTF16() bool {
600 return s[len(s)-1] == 'W'
603 // StrconvFunc returns name of Go string to OS string function for f.
604 func (f *Fn) StrconvFunc() string {
606 return syscalldot() + "UTF16PtrFromString"
608 return syscalldot() + "BytePtrFromString"
611 // StrconvType returns Go type name used for OS string for f.
612 func (f *Fn) StrconvType() string {
619 // HasStringParam is true, if f has at least one string parameter.
620 // Otherwise it is false.
621 func (f *Fn) HasStringParam() bool {
622 for _, p := range f.Params {
623 if p.Type == "string" {
630 // HelperName returns name of function f helper.
631 func (f *Fn) HelperName() string {
632 if !f.HasStringParam() {
638 // Source files and functions.
642 StdLibImports []string
643 ExternalImports []string
646 func (src *Source) Import(pkg string) {
647 src.StdLibImports = append(src.StdLibImports, pkg)
648 sort.Strings(src.StdLibImports)
651 func (src *Source) ExternalImport(pkg string) {
652 src.ExternalImports = append(src.ExternalImports, pkg)
653 sort.Strings(src.ExternalImports)
656 // ParseFiles parses files listed in fs and extracts all syscall
657 // functions listed in sys comments. It returns source files
658 // and functions collection *Source if successful.
659 func ParseFiles(fs []string) (*Source, error) {
661 Funcs: make([]*Fn, 0),
662 Files: make([]string, 0),
663 StdLibImports: []string{
666 ExternalImports: make([]string, 0),
668 for _, file := range fs {
669 if err := src.ParseFile(file); err != nil {
676 // DLLs return dll names for a source set src.
677 func (src *Source) DLLs() []string {
678 uniq := make(map[string]bool)
679 r := make([]string, 0)
680 for _, f := range src.Funcs {
682 if _, found := uniq[name]; !found {
691 // ParseFile adds additional file path to a source set src.
692 func (src *Source) ParseFile(path string) error {
693 file, err := os.Open(path)
699 s := bufio.NewScanner(file)
705 if !strings.HasPrefix(t, "//sys") {
709 if !(t[0] == ' ' || t[0] == '\t') {
712 f, err := newFn(t[1:])
716 src.Funcs = append(src.Funcs, f)
718 if err := s.Err(); err != nil {
721 src.Files = append(src.Files, path)
722 sort.Slice(src.Funcs, func(i, j int) bool {
723 fi, fj := src.Funcs[i], src.Funcs[j]
724 if fi.DLLName() == fj.DLLName() {
725 return fi.DLLFuncName() < fj.DLLFuncName()
727 return fi.DLLName() < fj.DLLName()
731 fset := token.NewFileSet()
732 _, err = file.Seek(0, 0)
736 pkg, err := parser.ParseFile(fset, "", file, parser.PackageClauseOnly)
740 packageName = pkg.Name.Name
745 // IsStdRepo reports whether src is part of standard library.
746 func (src *Source) IsStdRepo() (bool, error) {
747 if len(src.Files) == 0 {
748 return false, errors.New("no input files provided")
750 abspath, err := filepath.Abs(src.Files[0])
754 goroot := runtime.GOROOT()
755 if runtime.GOOS == "windows" {
756 abspath = strings.ToLower(abspath)
757 goroot = strings.ToLower(goroot)
759 sep := string(os.PathSeparator)
760 if !strings.HasSuffix(goroot, sep) {
763 return strings.HasPrefix(abspath, goroot), nil
766 // Generate output source file from a source set src.
767 func (src *Source) Generate(w io.Writer) error {
769 pkgStd = iota // any package in std library
770 pkgXSysWindows // x/sys/windows package
773 isStdRepo, err := src.IsStdRepo()
781 case packageName == "windows":
782 // TODO: this needs better logic than just using package name
783 pkgtype = pkgXSysWindows
790 src.Import("internal/syscall/windows/sysdll")
793 src.ExternalImport("golang.org/x/sys/windows")
796 if packageName != "syscall" {
797 src.Import("syscall")
799 funcMap := template.FuncMap{
800 "packagename": packagename,
801 "syscalldot": syscalldot,
802 "newlazydll": func(dll string) string {
803 arg := "\"" + dll + ".dll\""
805 return syscalldot() + "NewLazyDLL(" + arg + ")"
809 return syscalldot() + "NewLazyDLL(sysdll.Add(" + arg + "))"
811 return "NewLazySystemDLL(" + arg + ")"
813 return "windows.NewLazySystemDLL(" + arg + ")"
817 t := template.Must(template.New("main").Funcs(funcMap).Parse(srcTemplate))
818 err = t.Execute(w, src)
820 return errors.New("Failed to execute template: " + err.Error())
826 fmt.Fprintf(os.Stderr, "usage: mkwinsyscall [flags] [path ...]\n")
834 if len(flag.Args()) <= 0 {
835 fmt.Fprintf(os.Stderr, "no files to parse provided\n")
839 src, err := ParseFiles(flag.Args())
845 if err := src.Generate(&buf); err != nil {
849 data, err := format.Source(buf.Bytes())
854 _, err = os.Stdout.Write(data)
856 err = ioutil.WriteFile(*filename, data, 0644)
863 // TODO: use println instead to print in the following template
864 const srcTemplate = `
866 {{define "main"}}// Code generated by 'go generate'; DO NOT EDIT.
868 package {{packagename}}
871 {{range .StdLibImports}}"{{.}}"
874 {{range .ExternalImports}}"{{.}}"
880 // Do the interface allocations only once for common
883 errnoERROR_IO_PENDING = 997
887 errERROR_IO_PENDING error = {{syscalldot}}Errno(errnoERROR_IO_PENDING)
888 errERROR_EINVAL error = {{syscalldot}}EINVAL
891 // errnoErr returns common boxed Errno values, to prevent
892 // allocations at runtime.
893 func errnoErr(e {{syscalldot}}Errno) error {
896 return errERROR_EINVAL
897 case errnoERROR_IO_PENDING:
898 return errERROR_IO_PENDING
900 // TODO: add more here, after collecting data on the common
901 // error values see on Windows. (perhaps when running
907 {{template "dlls" .}}
908 {{template "funcnames" .}})
909 {{range .Funcs}}{{if .HasStringParam}}{{template "helperbody" .}}{{end}}{{template "funcbody" .}}{{end}}
912 {{/* help functions */}}
914 {{define "dlls"}}{{range .DLLs}} mod{{.}} = {{newlazydll .}}
917 {{define "funcnames"}}{{range .Funcs}} proc{{.DLLFuncName}} = mod{{.DLLName}}.NewProc("{{.DLLFuncName}}")
920 {{define "helperbody"}}
921 func {{.Name}}({{.ParamList}}) {{template "results" .}}{
922 {{template "helpertmpvars" .}} return {{.HelperName}}({{.HelperCallParamList}})
926 {{define "funcbody"}}
927 func {{.HelperName}}({{.HelperParamList}}) {{template "results" .}}{
928 {{template "maybeabsent" .}} {{template "tmpvars" .}} {{template "syscall" .}} {{template "tmpvarsreadback" .}}
929 {{template "seterror" .}}{{template "printtrace" .}} return
933 {{define "helpertmpvars"}}{{range .Params}}{{if .TmpVarHelperCode}} {{.TmpVarHelperCode}}
934 {{end}}{{end}}{{end}}
936 {{define "maybeabsent"}}{{if .MaybeAbsent}}{{.MaybeAbsent}}
939 {{define "tmpvars"}}{{range .Params}}{{if .TmpVarCode}} {{.TmpVarCode}}
940 {{end}}{{end}}{{end}}
942 {{define "results"}}{{if .Rets.List}}{{.Rets.List}} {{end}}{{end}}
944 {{define "syscall"}}{{.Rets.SetReturnValuesCode}}{{.Syscall}}(proc{{.DLLFuncName}}.Addr(), {{.ParamCount}}, {{.SyscallParamList}}){{end}}
946 {{define "tmpvarsreadback"}}{{range .Params}}{{if .TmpVarReadbackCode}}
947 {{.TmpVarReadbackCode}}{{end}}{{end}}{{end}}
949 {{define "seterror"}}{{if .Rets.SetErrorCode}} {{.Rets.SetErrorCode}}
952 {{define "printtrace"}}{{if .PrintTrace}} print("SYSCALL: {{.Name}}(", {{.ParamPrintList}}") (", {{.Rets.PrintList}}")\n")