arch/mk_syscalls_linux.go (336 lines of code) (raw):
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you 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.
//go:build ignore
// +build ignore
package main
import (
"bufio"
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"os"
"path/filepath"
"regexp"
"sort"
"strconv"
"strings"
"text/template"
)
const (
baseURL = "https://raw.githubusercontent.com/torvalds/linux/"
)
// TemplateParams is the data used in evaluating the template.
type TemplateParams struct {
LinuxVersion string
Arches []Arch
}
// Arch contains all the syscalls for a single architecture.
type Arch struct {
Name string
Syscalls []*Syscall
}
// Syscall represents a single system call.
type Syscall struct {
Num int
Name string
}
const fileTemplate = `// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you 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.
// Code generated by mk_syscalls_linux.go - DO NOT EDIT.
package arch
// Based on Linux {{ .LinuxVersion }}.
{{ range $arch := .Arches }}
var syscalls{{ $arch.Name }} = map[int]string{
{{- range $s := $arch.Syscalls }}
{{ $s.Num }}: "{{ $s.Name }}",
{{- end }}
}
{{ end }}
`
var tmpl = template.Must(template.New("syscalls").Parse(fileTemplate))
type builderFunc func(dir string) (*Arch, error)
func buildARM(dir string) (*Arch, error) {
const (
tablePath = "/arch/arm/tools/syscall.tbl"
headerPath = "/arch/arm/include/uapi/asm/unistd.h" // ARM private syscalls.
armNrBase = 0x0f0000 // Base value for ARM private syscalls.
)
syscallsA, err := readSyscalls(tablePath, dir, func(line string) (*Syscall, error) {
if len(line) == 0 || strings.HasPrefix(line, "#") {
return nil, nil
}
fields := strings.Fields(line)
if len(fields) < 3 {
return nil, fmt.Errorf("unexpected line format: %v", line)
}
// Filter out ARM OABI. http://wiki.embeddedarm.com/wiki/EABI_vs_OABI
if fields[1] == "oabi" {
return nil, nil
}
num, err := strconv.Atoi(fields[0])
if err != nil {
return nil, fmt.Errorf("failed to parse syscall number: %v at '%v'", err, line)
}
return &Syscall{
Num: num,
Name: fields[2],
}, nil
})
if err != nil {
return nil, err
}
armUnistdSycallRegex := regexp.MustCompile(
`^#define __ARM_NR_(?P<syscall>[a-z0-9_]+)\s+\(__ARM_NR_BASE\+(?P<number>\d+)\)`)
syscallsB, err := readSyscalls(headerPath, dir, func(line string) (*Syscall, error) {
matches := armUnistdSycallRegex.FindStringSubmatch(line)
if len(matches) != 3 {
return nil, nil
}
num, err := strconv.Atoi(matches[2])
if err != nil {
return nil, fmt.Errorf("failed to parse syscall number: %v at '%v'", err, line)
}
num += armNrBase
return &Syscall{
Num: num,
Name: matches[1],
}, nil
})
if err != nil {
return nil, err
}
syscalls := append(syscallsA, syscallsB...)
sort.Slice(syscalls, func(i, j int) bool {
return syscalls[i].Num < syscalls[j].Num
})
return &Arch{
Name: "ARM",
Syscalls: syscalls,
}, nil
}
func buildAARCH64(dir string) (*Arch, error) {
const (
headerPath = "/include/uapi/asm-generic/unistd.h"
sentinel = "syscalls"
)
omit := map[string]bool{
// sync_file_range2 shares a syscall number with
// sync_file_range guarded by __ARCH_WANT_SYNC_FILE_RANGE2.
// It is not possible to generate a map with both.
"sync_file_range2": true,
}
armUnistdSycallRegex := regexp.MustCompile(
`^#define __NR(?:3264)?_(?P<syscall>[a-z0-9_]+)\s+(?P<number>\d+)`)
syscalls, err := readSyscalls(headerPath, dir, func(line string) (*Syscall, error) {
matches := armUnistdSycallRegex.FindStringSubmatch(line)
if len(matches) != 3 || matches[1] == sentinel || omit[matches[1]] {
return nil, nil
}
num, err := strconv.Atoi(matches[2])
if err != nil {
return nil, fmt.Errorf("failed to parse syscall number: %v at '%v'", err, line)
}
return &Syscall{
Num: num,
Name: matches[1],
}, nil
})
if err != nil {
return nil, err
}
sort.Slice(syscalls, func(i, j int) bool {
return syscalls[i].Num < syscalls[j].Num
})
return &Arch{
Name: "AARCH64",
Syscalls: syscalls,
}, nil
}
func build386(dir string) (*Arch, error) {
const path = "/arch/x86/entry/syscalls/syscall_32.tbl"
syscalls, err := readSyscalls(path, dir, func(line string) (*Syscall, error) {
if len(line) == 0 || strings.HasPrefix(line, "#") {
return nil, nil
}
fields := strings.Fields(line)
if len(fields) < 3 {
return nil, fmt.Errorf("unexpected line format: %v", line)
}
num, err := strconv.Atoi(fields[0])
if err != nil {
return nil, fmt.Errorf("failed to parse syscall number: %v at '%v'", err, line)
}
return &Syscall{
Num: num,
Name: fields[2],
}, nil
})
if err != nil {
return nil, err
}
sort.Slice(syscalls, func(i, j int) bool {
return syscalls[i].Num < syscalls[j].Num
})
return &Arch{
Name: "386",
Syscalls: syscalls,
}, nil
}
func buildX32(dir string) (*Arch, error) {
const path = "/arch/x86/entry/syscalls/syscall_64.tbl"
syscalls, err := readSyscalls(path, dir, func(line string) (*Syscall, error) {
if len(line) == 0 || strings.HasPrefix(line, "#") {
return nil, nil
}
fields := strings.Fields(line)
if len(fields) < 3 {
return nil, fmt.Errorf("unexpected line format: %v", line)
}
if fields[1] == "x64" {
return nil, nil
}
num, err := strconv.Atoi(fields[0])
if err != nil {
return nil, fmt.Errorf("failed to parse syscall number: %v at '%v'", err, line)
}
return &Syscall{
Num: num,
Name: fields[2],
}, nil
})
if err != nil {
return nil, err
}
sort.Slice(syscalls, func(i, j int) bool {
return syscalls[i].Num < syscalls[j].Num
})
return &Arch{
Name: "X32",
Syscalls: syscalls,
}, nil
}
func buildX86_64(dir string) (*Arch, error) {
const path = "/arch/x86/entry/syscalls/syscall_64.tbl"
syscalls, err := readSyscalls(path, dir, func(line string) (*Syscall, error) {
if len(line) == 0 || strings.HasPrefix(line, "#") {
return nil, nil
}
fields := strings.Fields(line)
if len(fields) < 3 {
return nil, fmt.Errorf("unexpected line format: %v", line)
}
if fields[1] == "x32" {
return nil, nil
}
num, err := strconv.Atoi(fields[0])
if err != nil {
return nil, fmt.Errorf("failed to parse syscall number: %v at '%v'", err, line)
}
return &Syscall{
Num: num,
Name: fields[2],
}, nil
})
if err != nil {
return nil, err
}
sort.Slice(syscalls, func(i, j int) bool {
return syscalls[i].Num < syscalls[j].Num
})
return &Arch{
Name: "X86_64",
Syscalls: syscalls,
}, nil
}
func readSyscalls(path string, dir string, parse func(line string) (*Syscall, error)) ([]*Syscall, error) {
url := baseURL + linuxVersion + path
src, err := downloadFile(url, dir)
if err != nil {
return nil, err
}
f, err := os.Open(src)
if err != nil {
return nil, err
}
defer f.Close()
var syscalls []*Syscall
s := bufio.NewScanner(bufio.NewReader(f))
for s.Scan() {
line := strings.TrimSpace(s.Text())
syscall, err := parse(line)
if err != nil {
return nil, err
}
if syscall != nil {
syscalls = append(syscalls, syscall)
}
}
return syscalls, s.Err()
}
func downloadFile(url, destinationDir string) (string, error) {
resp, err := http.Get(url)
if err != nil {
return "", fmt.Errorf("http get failed: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("download failed with http status %v", resp.StatusCode)
}
name := filepath.Join(destinationDir, filepath.Base(url))
f, err := os.Create(name)
if err != nil {
return "", fmt.Errorf("failed to create output file: %v", err)
}
_, err = io.Copy(f, resp.Body)
if err != nil {
return "", fmt.Errorf("failed to write file to disk: %v", err)
}
return name, nil
}
var (
outputFile string
linuxVersion string
)
func init() {
flag.StringVar(&outputFile, "out", "zsyscalls.go", "output file")
flag.StringVar(&linuxVersion, "version", "v6.14", "linux version (git tag)")
}
func main() {
flag.Parse()
// Make temporary work directory.
tmp, err := ioutil.TempDir("", "mk_linux_syscalls")
if err != nil {
log.Fatal(err)
}
defer os.RemoveAll(tmp)
params := TemplateParams{
LinuxVersion: linuxVersion,
}
// List of all builders.
builders := []builderFunc{
buildARM,
buildAARCH64,
build386,
buildX32,
buildX86_64,
}
// Build a syscall table for each architecture.
for _, b := range builders {
arch, err := b(tmp)
if err != nil {
log.Fatal(err)
}
params.Arches = append(params.Arches, *arch)
}
// Write the output file based on the template.
out, err := os.Create(outputFile)
if err != nil {
log.Fatal(err)
}
defer out.Close()
if err = tmpl.Execute(out, params); err != nil {
log.Fatal(err)
}
}