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