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 }