// Copyright 2019 The gVisor Authors. // // 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. package cmd import ( "context" "encoding/csv" "encoding/json" "fmt" "io" "os" "sort" "strconv" "text/tabwriter" "github.com/google/subcommands" "gvisor.dev/gvisor/pkg/sentry/kernel" "gvisor.dev/gvisor/runsc/flag" ) // Syscalls implements subcommands.Command for the "syscalls" command. type Syscalls struct { format string os string arch string filename string } // CompatibilityInfo is a map of system and architecture to compatibility doc. // Maps operating system to architecture to ArchInfo. type CompatibilityInfo map[string]map[string]ArchInfo // ArchInfo is compatibility doc for an architecture. type ArchInfo struct { // Syscalls maps syscall number for the architecture to the doc. Syscalls map[uintptr]SyscallDoc `json:"syscalls"` } // SyscallDoc represents a single item of syscall documentation. type SyscallDoc struct { Name string `json:"name"` num uintptr Support string `json:"support"` Note string `json:"note,omitempty"` URLs []string `json:"urls,omitempty"` } type outputFunc func(io.Writer, CompatibilityInfo) error var ( // The string name to use for printing compatibility for all OSes. osAll = "all" // The string name to use for printing compatibility for all architectures. archAll = "all" // A map of OS name to map of architecture name to syscall table. syscallTableMap = make(map[string]map[string]*kernel.SyscallTable) // A map of output type names to output functions. outputMap = map[string]outputFunc{ "table": outputTable, "json": outputJSON, "csv": outputCSV, } ) // Name implements subcommands.Command.Name. func (*Syscalls) Name() string { return "syscalls" } // Synopsis implements subcommands.Command.Synopsis. func (*Syscalls) Synopsis() string { return "Print compatibility information for syscalls." } // Usage implements subcommands.Command.Usage. func (*Syscalls) Usage() string { return `syscalls [options] - Print compatibility information for syscalls. ` } // SetFlags implements subcommands.Command.SetFlags. func (s *Syscalls) SetFlags(f *flag.FlagSet) { f.StringVar(&s.format, "format", "table", "Output format (table, csv, json).") f.StringVar(&s.os, "os", osAll, "The OS (e.g. linux)") f.StringVar(&s.arch, "arch", archAll, "The CPU architecture (e.g. amd64).") f.StringVar(&s.filename, "filename", "", "Output filename (otherwise stdout).") } // Execute implements subcommands.Command.Execute. func (s *Syscalls) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus { out, ok := outputMap[s.format] if !ok { Fatalf("Unsupported output format %q", s.format) } // Build map of all supported architectures. tables := kernel.SyscallTables() for _, t := range tables { osMap, ok := syscallTableMap[t.OS.String()] if !ok { osMap = make(map[string]*kernel.SyscallTable) syscallTableMap[t.OS.String()] = osMap } osMap[t.Arch.String()] = t } // Build a map of the architectures we want to output. info, err := getCompatibilityInfo(s.os, s.arch) if err != nil { Fatalf("%v", err) } w := os.Stdout // Default. if s.filename != "" { w, err = os.OpenFile(s.filename, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644) if err != nil { Fatalf("Error opening %q: %v", s.filename, err) } } if err := out(w, info); err != nil { Fatalf("Error writing output: %v", err) } return subcommands.ExitSuccess } // getCompatibilityInfo returns compatibility info for the given OS name and // architecture name. Supports the special name 'all' for OS and architecture that // specifies that all supported OSes or architectures should be included. func getCompatibilityInfo(osName string, archName string) (CompatibilityInfo, error) { info := CompatibilityInfo(make(map[string]map[string]ArchInfo)) if osName == osAll { // Special processing for the 'all' OS name. for osName := range syscallTableMap { info[osName] = make(map[string]ArchInfo) // osName is a specific OS name. if err := addToCompatibilityInfo(info, osName, archName); err != nil { return info, err } } } else { // osName is a specific OS name. info[osName] = make(map[string]ArchInfo) if err := addToCompatibilityInfo(info, osName, archName); err != nil { return info, err } } return info, nil } // addToCompatibilityInfo adds ArchInfo for the given specific OS name and // architecture name. Supports the special architecture name 'all' to specify // that all supported architectures for the OS should be included. func addToCompatibilityInfo(info CompatibilityInfo, osName string, archName string) error { if archName == archAll { // Special processing for the 'all' architecture name. for archName := range syscallTableMap[osName] { archInfo, err := getArchInfo(osName, archName) if err != nil { return err } info[osName][archName] = archInfo } } else { // archName is a specific architecture name. archInfo, err := getArchInfo(osName, archName) if err != nil { return err } info[osName][archName] = archInfo } return nil } // getArchInfo returns compatibility info for a specific OS and architecture. func getArchInfo(osName string, archName string) (ArchInfo, error) { info := ArchInfo{} info.Syscalls = make(map[uintptr]SyscallDoc) t, ok := syscallTableMap[osName][archName] if !ok { return info, fmt.Errorf("syscall table for %s/%s not found", osName, archName) } for num, sc := range t.Table { info.Syscalls[num] = SyscallDoc{ Name: sc.Name, num: num, Support: sc.SupportLevel.String(), Note: sc.Note, URLs: sc.URLs, } } return info, nil } // outputTable outputs the syscall info in tabular format. func outputTable(w io.Writer, info CompatibilityInfo) error { tw := tabwriter.NewWriter(w, 0, 0, 2, ' ', 0) // Linux for osName, osInfo := range info { for archName, archInfo := range osInfo { // Print the OS/arch fmt.Fprintf(w, "%s/%s:\n\n", osName, archName) // Sort the syscalls for output in the table. sortedCalls := []SyscallDoc{} for _, sc := range archInfo.Syscalls { sortedCalls = append(sortedCalls, sc) } sort.Slice(sortedCalls, func(i, j int) bool { return sortedCalls[i].num < sortedCalls[j].num }) // Write the header _, err := fmt.Fprintf(tw, "%s\t%s\t%s\t%s\n", "NUM", "NAME", "SUPPORT", "NOTE", ) if err != nil { return err } // Write each syscall entry for _, sc := range sortedCalls { _, err = fmt.Fprintf(tw, "%s\t%s\t%s\t%s\n", strconv.FormatInt(int64(sc.num), 10), sc.Name, sc.Support, sc.Note, ) if err != nil { return err } // Add issue urls to note. for _, url := range sc.URLs { _, err = fmt.Fprintf(tw, "%s\t%s\t%s\tSee: %s\t\n", "", "", "", url, ) if err != nil { return err } } } err = tw.Flush() if err != nil { return err } } } return nil } // outputJSON outputs the syscall info in JSON format. func outputJSON(w io.Writer, info CompatibilityInfo) error { e := json.NewEncoder(w) e.SetIndent("", " ") return e.Encode(info) } // numberedRow is aCSV row annotated by syscall number (used for sorting) type numberedRow struct { num uintptr row []string } // outputCSV outputs the syscall info in tabular format. func outputCSV(w io.Writer, info CompatibilityInfo) error { csvWriter := csv.NewWriter(w) // Linux for osName, osInfo := range info { for archName, archInfo := range osInfo { // Sort the syscalls for output in the table. sortedCalls := []numberedRow{} for _, sc := range archInfo.Syscalls { // Add issue urls to note. note := sc.Note for _, url := range sc.URLs { note = fmt.Sprintf("%s\nSee: %s", note, url) } sortedCalls = append(sortedCalls, numberedRow{ num: sc.num, row: []string{ osName, archName, strconv.FormatInt(int64(sc.num), 10), sc.Name, sc.Support, note, }, }) } sort.Slice(sortedCalls, func(i, j int) bool { return sortedCalls[i].num < sortedCalls[j].num }) // Write the header err := csvWriter.Write([]string{ "OS", "Arch", "Num", "Name", "Support", "Note", }) if err != nil { return err } // Write each syscall entry for _, sc := range sortedCalls { err = csvWriter.Write(sc.row) if err != nil { return err } } csvWriter.Flush() err = csvWriter.Error() if err != nil { return err } } } return nil }