summaryrefslogtreecommitdiffhomepage
path: root/cmd/parse-syscall-annotations/main.go
diff options
context:
space:
mode:
authorIan Lewis <ianmlewis@gmail.com>2019-03-29 22:40:11 -0400
committerIan Lewis <ianmlewis@gmail.com>2019-03-29 22:40:11 -0400
commit22f1890a9beab11d8cfdceba3a4d66f8bbbb468c (patch)
tree110ec3a84a72560244ee4476852295b86a737eb0 /cmd/parse-syscall-annotations/main.go
Initial commit
Diffstat (limited to 'cmd/parse-syscall-annotations/main.go')
-rw-r--r--cmd/parse-syscall-annotations/main.go346
1 files changed, 346 insertions, 0 deletions
diff --git a/cmd/parse-syscall-annotations/main.go b/cmd/parse-syscall-annotations/main.go
new file mode 100644
index 000000000..0b7954a6c
--- /dev/null
+++ b/cmd/parse-syscall-annotations/main.go
@@ -0,0 +1,346 @@
+// Copyright 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// This program will take a single golang source file, or a directory containing
+// many source files and produce a JSON output which represent any comments
+// containing compatibility metadata.
+
+// Command parse-syscall-annotations parses syscall annotations from Godoc and
+// generates a JSON file with the parsed syscall info.
+//
+// Annotations take the form:
+// @Syscall(<name>, <arg>:<value>, ...)
+//
+// Supported args and values are:
+// - arg: A syscall option. This entry only applies to the syscall when given this option.
+// - support: Indicates support level
+// - FULL: Full support
+// - PARTIAL: Partial support. Details should be provided in note.
+// - UNIMPLEMENTED: Unimplemented
+// - returns: Indicates a known return value. Implies PARTIAL support. Values are syscall errors.
+// This is treated as a string so you can use something like "returns:EPERM or ENOSYS".
+// - issue: A GitHub issue number.
+// - note: A note
+
+package main
+
+import (
+ "encoding/json"
+ "flag"
+ "fmt"
+ "go/ast"
+ "go/parser"
+ "go/token"
+ "log"
+ "os"
+ "path/filepath"
+ "regexp"
+ "sort"
+ "strings"
+ "text/template"
+)
+
+var (
+ srcDir = flag.String("dir", "./", "The source directory")
+ jsonOut = flag.Bool("json", false, "Output info as json")
+
+ r *regexp.Regexp
+ r2 *regexp.Regexp
+
+ mdTemplate = template.Must(template.New("name").Parse(`+++
+title = "Syscall Reference"
+description = "Syscall Compatibility Reference Documentation"
+weight = 10
++++
+
+This table is a reference of Linux syscalls and their compatibility status in
+gVisor. gVisor does not support all syscalls and some syscalls may have a
+partial implementation.
+
+Of {{ .Total }} syscalls, {{ .Implemented }} syscalls have a full or partial
+implementation. There are currently {{ .Unimplemented }} unimplemented
+syscalls. {{ .Unknown }} syscalls are not yet documented.
+
+<table>
+ <thead>
+ <tr>
+ <th>#</th>
+ <th>Name</th>
+ <th>Support</th>
+ <th>GitHub Issue</th>
+ <th>Notes</th>
+ </tr>
+ </thead>
+ <tbody>{{ range .Syscalls }}{{ if ne .Support "Unknown" }}
+ <tr>
+ <td><a class="doc-table-anchor" id="{{ .Name }}{{ if index .Metadata "arg" }}({{ index .Metadata "arg" }}){{ end }}"></a>{{ .Number }}</td>
+ <td><a href="http://man7.org/linux/man-pages/man2/{{ .Name }}.2.html" target="_blank" rel="noopener">{{ .Name }}{{ if index .Metadata "arg" }}({{ index .Metadata "arg" }}){{ end }}</a></td>
+ <td>{{ .Support }}</td>
+ <td>{{ if index .Metadata "issue" }}<a href="https://github.com/google/gvisor/issues/{{ index .Metadata "issue" }}">#{{ index .Metadata "issue" }}</a>{{ end }}</td>
+ <td>{{ .Note }}</td>
+ </tr>{{ end }}{{ end }}
+ </tbody>
+</table>
+`))
+)
+
+// Syscall represents a function implementation of a syscall.
+type Syscall struct {
+ File string
+ Line int
+
+ Number int
+ Name string
+
+ Metadata map[string]string
+}
+
+const (
+ UNKNOWN = iota
+ UNIMPLEMENTED
+ PARTIAL_SUPPORT
+ FULL_SUPPORT
+)
+
+func (s *Syscall) SupportLevel() int {
+ supportLevel := UNKNOWN
+ switch strings.ToUpper(s.Metadata["support"]) {
+ case "FULL":
+ supportLevel = FULL_SUPPORT
+ case "PARTIAL":
+ supportLevel = PARTIAL_SUPPORT
+ case "UNIMPLEMENTED":
+ supportLevel = UNIMPLEMENTED
+ }
+
+ // If an arg or returns is specifed treat that as a partial implementation even if
+ // there is full support for the argument.
+ if s.Metadata["arg"] != "" {
+ supportLevel = PARTIAL_SUPPORT
+ }
+ if s.Metadata["returns"] != "" && supportLevel == UNKNOWN {
+ returns := strings.ToUpper(s.Metadata["returns"])
+ // Default to PARTIAL support if only returns is specified
+ supportLevel = PARTIAL_SUPPORT
+
+ // If ENOSYS is returned unequivically, treat it as unimplemented.
+ if returns == "ENOSYS" {
+ supportLevel = UNIMPLEMENTED
+ }
+ }
+
+ return supportLevel
+}
+
+func (s *Syscall) Support() string {
+ l := s.SupportLevel()
+ switch l {
+ case FULL_SUPPORT:
+ return "Full"
+ case PARTIAL_SUPPORT:
+ return "Partial"
+ case UNIMPLEMENTED:
+ return "Unimplemented"
+ default:
+ return "Unknown"
+ }
+}
+
+func (s *Syscall) Note() string {
+ note := s.Metadata["note"]
+ returns := s.Metadata["returns"]
+ // Add "Returns ENOSYS" note by default if support:UNIMPLEMENTED
+ if returns == "" && s.SupportLevel() == UNIMPLEMENTED {
+ returns = "ENOSYS"
+ }
+ if returns != "" {
+ return_note := fmt.Sprintf("Returns %s", returns)
+ if note != "" {
+ note = return_note + "; " + note
+ } else {
+ note = return_note
+ }
+ }
+ if note == "" && s.SupportLevel() == FULL_SUPPORT {
+ note = "Full Support"
+ }
+ return note
+}
+
+type Report struct {
+ Implemented int
+ Unimplemented int
+ Unknown int
+ Total int
+ Syscalls []*Syscall
+}
+
+func init() {
+ // Build a regex that will attempt to match all fields in tokens.
+
+ // Regexp for matching syscall definitions
+ s := "@Syscall\\(([^\\),]+)([^\\)]+)\\)"
+ r = regexp.MustCompile(s)
+
+ // Regexp for matching metadata
+ s2 := "([^\\ ),]+):([^\\),]+)"
+ r2 = regexp.MustCompile(s2)
+
+ ReverseSyscallMap = make(map[string]int)
+ for no, name := range SyscallMap {
+ ReverseSyscallMap[name] = no
+ }
+}
+
+// parseDoc parses all comments in a file and returns the parsed syscall
+// information.
+func parseDoc(fs *token.FileSet, f *ast.File) []*Syscall {
+ syscalls := []*Syscall{}
+ for _, cg := range f.Comments {
+ for _, line := range strings.Split(cg.Text(), "\n") {
+ if syscall := parseLine(fs, line); syscall != nil {
+ pos := fs.Position(cg.Pos())
+ syscall.File = pos.Filename
+ syscall.Line = pos.Line
+
+ syscalls = append(syscalls, syscall)
+ }
+ }
+ }
+ return syscalls
+}
+
+// parseLine parses a single line of Godoc and returns the parsed syscall
+// information. If no information is found, nil is returned.
+// Syscall declarations take the form:
+// @Syscall(<name>, <verb>:<value>, ...)
+func parseLine(fs *token.FileSet, line string) *Syscall {
+ s := r.FindAllStringSubmatch(line, -1)
+ if len(s) > 0 {
+ name := strings.ToLower(s[0][1])
+ if n, ok := ReverseSyscallMap[name]; ok {
+ syscall := Syscall{}
+ syscall.Name = name
+ syscall.Number = n
+ syscall.Metadata = make(map[string]string)
+ s2 := r2.FindAllStringSubmatch(s[0][2], -1)
+ for _, match := range s2 {
+ syscall.Metadata[match[1]] = match[2]
+ }
+ return &syscall
+ } else {
+ log.Printf("Warning: unknown syscall %q", name)
+ }
+ }
+ return nil
+}
+
+func main() {
+ flag.Parse()
+
+ var syscalls []*Syscall
+
+ err := filepath.Walk(*srcDir, func(path string, info os.FileInfo, err error) error {
+ if info != nil && info.IsDir() {
+ fs := token.NewFileSet()
+ d, err := parser.ParseDir(fs, path, nil, parser.ParseComments)
+ if err != nil {
+ return err
+ }
+
+ for _, p := range d {
+ for _, f := range p.Files {
+ s := parseDoc(fs, f)
+ syscalls = append(syscalls, s...)
+ }
+ }
+ }
+
+ return nil
+ })
+
+ if err != nil {
+ fmt.Printf("failed to walk dir %s: %v", *srcDir, err)
+ os.Exit(1)
+ }
+
+ var fullList []*Syscall
+ for no, name := range SyscallMap {
+ found := false
+ for _, s := range syscalls {
+ if s.Number == no {
+ fullList = append(fullList, s)
+ found = true
+ }
+ }
+ if !found {
+ fullList = append(fullList, &Syscall{
+ Name: name,
+ Number: no,
+ })
+ }
+ }
+
+ // Sort the syscalls by number.
+ sort.Slice(fullList, func(i, j int) bool {
+ return fullList[i].Number < fullList[j].Number
+ })
+
+ if *jsonOut {
+ j, err := json.Marshal(fullList)
+ if err != nil {
+ fmt.Printf("failed to marshal JSON: %v", err)
+ os.Exit(1)
+ }
+ os.Stdout.Write(j)
+ return
+ }
+
+ // Count syscalls and group by syscall number and support level
+ supportMap := map[int]int{}
+ for _, s := range fullList {
+ supportLevel := s.SupportLevel()
+
+ // If we already have set a higher level of support
+ // keep the current value
+ if current, ok := supportMap[s.Number]; ok && supportLevel < current {
+ continue
+ }
+
+ supportMap[s.Number] = supportLevel
+ }
+ report := Report{
+ Syscalls: fullList,
+ }
+ for _, s := range supportMap {
+ switch s {
+ case FULL_SUPPORT:
+ report.Implemented += 1
+ case PARTIAL_SUPPORT:
+ report.Implemented += 1
+ case UNIMPLEMENTED:
+ report.Unimplemented += 1
+ case UNKNOWN:
+ report.Unknown += 1
+ }
+ report.Total += 1
+ }
+
+ err = mdTemplate.Execute(os.Stdout, report)
+ if err != nil {
+ fmt.Printf("failed to execute template: %v", err)
+ os.Exit(1)
+ return
+ }
+}