diff options
Diffstat (limited to 'cmd/parse-syscall-annotations')
-rw-r--r-- | cmd/parse-syscall-annotations/.gitignore | 1 | ||||
-rw-r--r-- | cmd/parse-syscall-annotations/main.go | 346 | ||||
-rw-r--r-- | cmd/parse-syscall-annotations/syscall.go | 359 |
3 files changed, 706 insertions, 0 deletions
diff --git a/cmd/parse-syscall-annotations/.gitignore b/cmd/parse-syscall-annotations/.gitignore new file mode 100644 index 000000000..ecfe5c996 --- /dev/null +++ b/cmd/parse-syscall-annotations/.gitignore @@ -0,0 +1 @@ +parse-syscall-annotations 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 + } +} diff --git a/cmd/parse-syscall-annotations/syscall.go b/cmd/parse-syscall-annotations/syscall.go new file mode 100644 index 000000000..b4a756440 --- /dev/null +++ b/cmd/parse-syscall-annotations/syscall.go @@ -0,0 +1,359 @@ +// 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. + +package main + +// ReverseSyscallMap is a map of syscall name (lowercase) to number +var ReverseSyscallMap map[string]int + +// SyscallMap is a map of syscall number to syscall name (lowercase) +var SyscallMap = map[int]string{ + 0: "read", + 1: "write", + 2: "open", + 3: "close", + 4: "stat", + 5: "fstat", + 6: "lstat", + 7: "poll", + 8: "lseek", + 9: "mmap", + 10: "mprotect", + 11: "munmap", + 12: "brk", + 13: "rtsigaction", + 14: "rtsigprocmask", + 15: "rtsigreturn", + 16: "ioctl", + 17: "pread64", + 18: "pwrite64", + 19: "readv", + 20: "writev", + 21: "access", + 22: "pipe", + 23: "select", + 24: "schedyield", + 25: "mremap", + 26: "msync", + 27: "mincore", + 28: "madvise", + 29: "shmget", + 30: "shmat", + 31: "shmctl", + 32: "dup", + 33: "dup2", + 34: "pause", + 35: "nanosleep", + 36: "getitimer", + 37: "alarm", + 38: "setitimer", + 39: "getpid", + 40: "sendfile", + 41: "socket", + 42: "connect", + 43: "accept", + 44: "sendto", + 45: "recvfrom", + 46: "sendmsg", + 47: "recvmsg", + 48: "shutdown", + 49: "bind", + 50: "listen", + 51: "getsockname", + 52: "getpeername", + 53: "socketpair", + 54: "setsockopt", + 55: "getsockopt", + 56: "clone", + 57: "fork", + 58: "vfork", + 59: "execve", + 60: "exit", + 61: "wait4", + 62: "kill", + 63: "uname", + 64: "semget", + 65: "semop", + 66: "semctl", + 67: "shmdt", + 68: "msgget", + 69: "msgsnd", + 70: "msgrcv", + 71: "msgctl", + 72: "fcntl", + 73: "flock", + 74: "fsync", + 75: "fdatasync", + 76: "truncate", + 77: "ftruncate", + 78: "getdents", + 79: "getcwd", + 80: "chdir", + 81: "fchdir", + 82: "rename", + 83: "mkdir", + 84: "rmdir", + 85: "creat", + 86: "link", + 87: "unlink", + 88: "symlink", + 89: "readlink", + 90: "chmod", + 91: "fchmod", + 92: "chown", + 93: "fchown", + 94: "lchown", + 95: "umask", + 96: "gettimeofday", + 97: "getrlimit", + 98: "getrusage", + 99: "sysinfo", + 100: "times", + 101: "ptrace", + 102: "getuid", + 103: "syslog", + 104: "getgid", + 105: "setuid", + 106: "setgid", + 107: "geteuid", + 108: "getegid", + 109: "setpgid", + 110: "getppid", + 111: "getpgrp", + 112: "setsid", + 113: "setreuid", + 114: "setregid", + 115: "getgroups", + 116: "setgroups", + 117: "setresuid", + 118: "getresuid", + 119: "setresgid", + 120: "getresgid", + 121: "getpgid", + 122: "setfsuid", + 123: "setfsgid", + 124: "getsid", + 125: "capget", + 126: "capset", + 127: "rtsigpending", + 128: "rtsigtimedwait", + 129: "rtsigqueueinfo", + 130: "rtsigsuspend", + 131: "sigaltstack", + 132: "utime", + 133: "mknod", + 134: "uselib", + 135: "setpersonality", + 136: "ustat", + 137: "statfs", + 138: "fstatfs", + 139: "sysfs", + 140: "getpriority", + 141: "setpriority", + 142: "schedsetparam", + 143: "schedgetparam", + 144: "schedsetscheduler", + 145: "schedgetscheduler", + 146: "schedgetprioritymax", + 147: "schedgetprioritymin", + 148: "schedrrgetinterval", + 149: "mlock", + 150: "munlock", + 151: "mlockall", + 152: "munlockall", + 153: "vhangup", + 154: "modifyldt", + 155: "pivotroot", + 156: "sysctl", + 157: "prctl", + 158: "archprctl", + 159: "adjtimex", + 160: "setrlimit", + 161: "chroot", + 162: "sync", + 163: "acct", + 164: "settimeofday", + 165: "mount", + 166: "umount2", + 167: "swapon", + 168: "swapoff", + 169: "reboot", + 170: "sethostname", + 171: "setdomainname", + 172: "iopl", + 173: "ioperm", + 174: "createmodule", + 175: "initmodule", + 176: "deletemodule", + 177: "getkernelsyms", + 178: "querymodule", + 179: "quotactl", + 180: "nfsservctl", + 181: "getpmsg", + 182: "putpmsg", + 183: "afssyscall", + 184: "tuxcall", + 185: "security", + 186: "gettid", + 187: "readahead", + 188: "setxattr", + 189: "lsetxattr", + 190: "fsetxattr", + 191: "getxattr", + 192: "lgetxattr", + 193: "fgetxattr", + 194: "listxattr", + 195: "llistxattr", + 196: "flistxattr", + 197: "removexattr", + 198: "lremovexattr", + 199: "fremovexattr", + 200: "tkill", + 201: "time", + 202: "futex", + 203: "schedsetaffinity", + 204: "schedgetaffinity", + 205: "setthreadarea", + 206: "iosetup", + 207: "iodestroy", + 208: "iogetevents", + 209: "iosubmit", + 210: "iocancel", + 211: "getthreadarea", + 212: "lookupdcookie", + 213: "epollcreate", + 214: "epollctlold", + 215: "epollwaitold", + 216: "remapfilepages", + 217: "getdents64", + 218: "settidaddress", + 219: "restartsyscall", + 220: "semtimedop", + 221: "fadvise64", + 222: "timercreate", + 223: "timersettime", + 224: "timergettime", + 225: "timergetoverrun", + 226: "timerdelete", + 227: "clocksettime", + 228: "clockgettime", + 229: "clockgetres", + 230: "clocknanosleep", + 231: "exitgroup", + 232: "epollwait", + 233: "epollctl", + 234: "tgkill", + 235: "utimes", + 236: "vserver", + 237: "mbind", + 238: "setmempolicy", + 239: "getmempolicy", + 240: "mqopen", + 241: "mqunlink", + 242: "mqtimedsend", + 243: "mqtimedreceive", + 244: "mqnotify", + 245: "mqgetsetattr", + 246: "kexec_load", + 247: "waitid", + 248: "addkey", + 249: "requestkey", + 250: "keyctl", + 251: "ioprioset", + 252: "ioprioget", + 253: "inotifyinit", + 254: "inotifyaddwatch", + 255: "inotifyrmwatch", + 256: "migratepages", + 257: "openat", + 258: "mkdirat", + 259: "mknodat", + 260: "fchownat", + 261: "futimesat", + 262: "fstatat", + 263: "unlinkat", + 264: "renameat", + 265: "linkat", + 266: "symlinkat", + 267: "readlinkat", + 268: "fchmodat", + 269: "faccessat", + 270: "pselect", + 271: "ppoll", + 272: "unshare", + 273: "setrobustlist", + 274: "getrobustlist", + 275: "splice", + 276: "tee", + 277: "syncfilerange", + 278: "vmsplice", + 279: "movepages", + 280: "utimensat", + 281: "epollpwait", + 282: "signalfd", + 283: "timerfdcreate", + 284: "eventfd", + 285: "fallocate", + 286: "timerfdsettime", + 287: "timerfdgettime", + 288: "accept4", + 289: "signalfd4", + 290: "eventfd2", + 291: "epollcreate1", + 292: "dup3", + 293: "pipe2", + 294: "inotifyinit1", + 295: "preadv", + 296: "pwritev", + 297: "rttgsigqueueinfo", + 298: "perfeventopen", + 299: "recvmmsg", + 300: "fanotifyinit", + 301: "fanotifymark", + 302: "prlimit64", + 303: "nametohandleat", + 304: "openbyhandleat", + 305: "clockadjtime", + 306: "syncfs", + 307: "sendmmsg", + 308: "setns", + 309: "getcpu", + 310: "processvmreadv", + 311: "processvmwritev", + 312: "kcmp", + 313: "finitmodule", + 314: "schedsetattr", + 315: "schedgetattr", + 316: "renameat2", + 317: "seccomp", + 318: "getrandom", + 319: "memfdcreate", + 320: "kexecfileload", + 321: "bpf", + 322: "execveat", + 323: "userfaultfd", + 324: "membarrier", + 325: "mlock2", + // syscalls after 325 are "backports" from versions of linux after 4.4. + 326: "copyfilerange", + 327: "preadv2", + 328: "pwritev2", +} |