github.com/MerlinKodo/gvisor@v0.0.0-20231110090155-957f62ecf90e/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/MerlinKodo/gvisor/pkg/sentry/kernel" 29 "github.com/MerlinKodo/gvisor/runsc/cmd/util" 30 "github.com/MerlinKodo/gvisor/runsc/flag" 31 "github.com/google/subcommands" 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 }