github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/runsc/cmd/syscalls.go (about)

     1  // Copyright 2019 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package cmd
    16  
    17  import (
    18  	"context"
    19  	"encoding/csv"
    20  	"encoding/json"
    21  	"fmt"
    22  	"io"
    23  	"os"
    24  	"sort"
    25  	"strconv"
    26  	"text/tabwriter"
    27  
    28  	"github.com/google/subcommands"
    29  	"github.com/SagerNet/gvisor/pkg/sentry/kernel"
    30  	"github.com/SagerNet/gvisor/runsc/flag"
    31  )
    32  
    33  // Syscalls implements subcommands.Command for the "syscalls" command.
    34  type Syscalls struct {
    35  	format   string
    36  	os       string
    37  	arch     string
    38  	filename string
    39  }
    40  
    41  // CompatibilityInfo is a map of system and architecture to compatibility doc.
    42  // Maps operating system to architecture to ArchInfo.
    43  type CompatibilityInfo map[string]map[string]ArchInfo
    44  
    45  // ArchInfo is compatibility doc for an architecture.
    46  type ArchInfo struct {
    47  	// Syscalls maps syscall number for the architecture to the doc.
    48  	Syscalls map[uintptr]SyscallDoc `json:"syscalls"`
    49  }
    50  
    51  // SyscallDoc represents a single item of syscall documentation.
    52  type SyscallDoc struct {
    53  	Name string `json:"name"`
    54  	num  uintptr
    55  
    56  	Support string   `json:"support"`
    57  	Note    string   `json:"note,omitempty"`
    58  	URLs    []string `json:"urls,omitempty"`
    59  }
    60  
    61  type outputFunc func(io.Writer, CompatibilityInfo) error
    62  
    63  var (
    64  	// The string name to use for printing compatibility for all OSes.
    65  	osAll = "all"
    66  
    67  	// The string name to use for printing compatibility for all architectures.
    68  	archAll = "all"
    69  
    70  	// A map of OS name to map of architecture name to syscall table.
    71  	syscallTableMap = make(map[string]map[string]*kernel.SyscallTable)
    72  
    73  	// A map of output type names to output functions.
    74  	outputMap = map[string]outputFunc{
    75  		"table": outputTable,
    76  		"json":  outputJSON,
    77  		"csv":   outputCSV,
    78  	}
    79  )
    80  
    81  // Name implements subcommands.Command.Name.
    82  func (*Syscalls) Name() string {
    83  	return "syscalls"
    84  }
    85  
    86  // Synopsis implements subcommands.Command.Synopsis.
    87  func (*Syscalls) Synopsis() string {
    88  	return "Print compatibility information for syscalls."
    89  }
    90  
    91  // Usage implements subcommands.Command.Usage.
    92  func (*Syscalls) Usage() string {
    93  	return `syscalls [options] - Print compatibility information for syscalls.
    94  `
    95  }
    96  
    97  // SetFlags implements subcommands.Command.SetFlags.
    98  func (s *Syscalls) SetFlags(f *flag.FlagSet) {
    99  	f.StringVar(&s.format, "format", "table", "Output format (table, csv, json).")
   100  	f.StringVar(&s.os, "os", osAll, "The OS (e.g. linux)")
   101  	f.StringVar(&s.arch, "arch", archAll, "The CPU architecture (e.g. amd64).")
   102  	f.StringVar(&s.filename, "filename", "", "Output filename (otherwise stdout).")
   103  }
   104  
   105  // Execute implements subcommands.Command.Execute.
   106  func (s *Syscalls) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus {
   107  	out, ok := outputMap[s.format]
   108  	if !ok {
   109  		Fatalf("Unsupported output format %q", s.format)
   110  	}
   111  
   112  	// Build map of all supported architectures.
   113  	tables := kernel.SyscallTables()
   114  	for _, t := range tables {
   115  		osMap, ok := syscallTableMap[t.OS.String()]
   116  		if !ok {
   117  			osMap = make(map[string]*kernel.SyscallTable)
   118  			syscallTableMap[t.OS.String()] = osMap
   119  		}
   120  		osMap[t.Arch.String()] = t
   121  	}
   122  
   123  	// Build a map of the architectures we want to output.
   124  	info, err := getCompatibilityInfo(s.os, s.arch)
   125  	if err != nil {
   126  		Fatalf("%v", err)
   127  	}
   128  
   129  	w := os.Stdout // Default.
   130  	if s.filename != "" {
   131  		w, err = os.OpenFile(s.filename, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
   132  		if err != nil {
   133  			Fatalf("Error opening %q: %v", s.filename, err)
   134  		}
   135  	}
   136  	if err := out(w, info); err != nil {
   137  		Fatalf("Error writing output: %v", err)
   138  	}
   139  
   140  	return subcommands.ExitSuccess
   141  }
   142  
   143  // getCompatibilityInfo returns compatibility info for the given OS name and
   144  // architecture name. Supports the special name 'all' for OS and architecture that
   145  // specifies that all supported OSes or architectures should be included.
   146  func getCompatibilityInfo(osName string, archName string) (CompatibilityInfo, error) {
   147  	info := CompatibilityInfo(make(map[string]map[string]ArchInfo))
   148  	if osName == osAll {
   149  		// Special processing for the 'all' OS name.
   150  		for osName := range syscallTableMap {
   151  			info[osName] = make(map[string]ArchInfo)
   152  			// osName is a specific OS name.
   153  			if err := addToCompatibilityInfo(info, osName, archName); err != nil {
   154  				return info, err
   155  			}
   156  		}
   157  	} else {
   158  		// osName is a specific OS name.
   159  		info[osName] = make(map[string]ArchInfo)
   160  		if err := addToCompatibilityInfo(info, osName, archName); err != nil {
   161  			return info, err
   162  		}
   163  	}
   164  
   165  	return info, nil
   166  }
   167  
   168  // addToCompatibilityInfo adds ArchInfo for the given specific OS name and
   169  // architecture name. Supports the special architecture name 'all' to specify
   170  // that all supported architectures for the OS should be included.
   171  func addToCompatibilityInfo(info CompatibilityInfo, osName string, archName string) error {
   172  	if archName == archAll {
   173  		// Special processing for the 'all' architecture name.
   174  		for archName := range syscallTableMap[osName] {
   175  			archInfo, err := getArchInfo(osName, archName)
   176  			if err != nil {
   177  				return err
   178  			}
   179  			info[osName][archName] = archInfo
   180  		}
   181  	} else {
   182  		// archName is a specific architecture name.
   183  		archInfo, err := getArchInfo(osName, archName)
   184  		if err != nil {
   185  			return err
   186  		}
   187  		info[osName][archName] = archInfo
   188  	}
   189  
   190  	return nil
   191  }
   192  
   193  // getArchInfo returns compatibility info for a specific OS and architecture.
   194  func getArchInfo(osName string, archName string) (ArchInfo, error) {
   195  	info := ArchInfo{}
   196  	info.Syscalls = make(map[uintptr]SyscallDoc)
   197  
   198  	t, ok := syscallTableMap[osName][archName]
   199  	if !ok {
   200  		return info, fmt.Errorf("syscall table for %s/%s not found", osName, archName)
   201  	}
   202  
   203  	for num, sc := range t.Table {
   204  		info.Syscalls[num] = SyscallDoc{
   205  			Name:    sc.Name,
   206  			num:     num,
   207  			Support: sc.SupportLevel.String(),
   208  			Note:    sc.Note,
   209  			URLs:    sc.URLs,
   210  		}
   211  	}
   212  
   213  	return info, nil
   214  }
   215  
   216  // outputTable outputs the syscall info in tabular format.
   217  func outputTable(w io.Writer, info CompatibilityInfo) error {
   218  	tw := tabwriter.NewWriter(w, 0, 0, 2, ' ', 0)
   219  
   220  	// Linux
   221  	for osName, osInfo := range info {
   222  		for archName, archInfo := range osInfo {
   223  			// Print the OS/arch
   224  			fmt.Fprintf(w, "%s/%s:\n\n", osName, archName)
   225  
   226  			// Sort the syscalls for output in the table.
   227  			sortedCalls := []SyscallDoc{}
   228  			for _, sc := range archInfo.Syscalls {
   229  				sortedCalls = append(sortedCalls, sc)
   230  			}
   231  			sort.Slice(sortedCalls, func(i, j int) bool {
   232  				return sortedCalls[i].num < sortedCalls[j].num
   233  			})
   234  
   235  			// Write the header
   236  			_, err := fmt.Fprintf(tw, "%s\t%s\t%s\t%s\n",
   237  				"NUM",
   238  				"NAME",
   239  				"SUPPORT",
   240  				"NOTE",
   241  			)
   242  			if err != nil {
   243  				return err
   244  			}
   245  
   246  			// Write each syscall entry
   247  			for _, sc := range sortedCalls {
   248  				_, err = fmt.Fprintf(tw, "%s\t%s\t%s\t%s\n",
   249  					strconv.FormatInt(int64(sc.num), 10),
   250  					sc.Name,
   251  					sc.Support,
   252  					sc.Note,
   253  				)
   254  				if err != nil {
   255  					return err
   256  				}
   257  				// Add issue urls to note.
   258  				for _, url := range sc.URLs {
   259  					_, err = fmt.Fprintf(tw, "%s\t%s\t%s\tSee: %s\t\n",
   260  						"",
   261  						"",
   262  						"",
   263  						url,
   264  					)
   265  					if err != nil {
   266  						return err
   267  					}
   268  				}
   269  			}
   270  
   271  			err = tw.Flush()
   272  			if err != nil {
   273  				return err
   274  			}
   275  		}
   276  	}
   277  
   278  	return nil
   279  }
   280  
   281  // outputJSON outputs the syscall info in JSON format.
   282  func outputJSON(w io.Writer, info CompatibilityInfo) error {
   283  	e := json.NewEncoder(w)
   284  	e.SetIndent("", "  ")
   285  	return e.Encode(info)
   286  }
   287  
   288  // numberedRow is aCSV row annotated by syscall number (used for sorting)
   289  type numberedRow struct {
   290  	num uintptr
   291  	row []string
   292  }
   293  
   294  // outputCSV outputs the syscall info in tabular format.
   295  func outputCSV(w io.Writer, info CompatibilityInfo) error {
   296  	csvWriter := csv.NewWriter(w)
   297  
   298  	// Linux
   299  	for osName, osInfo := range info {
   300  		for archName, archInfo := range osInfo {
   301  			// Sort the syscalls for output in the table.
   302  			sortedCalls := []numberedRow{}
   303  			for _, sc := range archInfo.Syscalls {
   304  				// Add issue urls to note.
   305  				note := sc.Note
   306  				for _, url := range sc.URLs {
   307  					note = fmt.Sprintf("%s\nSee: %s", note, url)
   308  				}
   309  
   310  				sortedCalls = append(sortedCalls, numberedRow{
   311  					num: sc.num,
   312  					row: []string{
   313  						osName,
   314  						archName,
   315  						strconv.FormatInt(int64(sc.num), 10),
   316  						sc.Name,
   317  						sc.Support,
   318  						note,
   319  					},
   320  				})
   321  			}
   322  			sort.Slice(sortedCalls, func(i, j int) bool {
   323  				return sortedCalls[i].num < sortedCalls[j].num
   324  			})
   325  
   326  			// Write the header
   327  			err := csvWriter.Write([]string{
   328  				"OS",
   329  				"Arch",
   330  				"Num",
   331  				"Name",
   332  				"Support",
   333  				"Note",
   334  			})
   335  			if err != nil {
   336  				return err
   337  			}
   338  
   339  			// Write each syscall entry
   340  			for _, sc := range sortedCalls {
   341  				err = csvWriter.Write(sc.row)
   342  				if err != nil {
   343  					return err
   344  				}
   345  			}
   346  
   347  			csvWriter.Flush()
   348  			err = csvWriter.Error()
   349  			if err != nil {
   350  				return err
   351  			}
   352  		}
   353  	}
   354  
   355  	return nil
   356  }