package main import ( "fmt" "log" "os" "time" "github.com/iancoleman/strcase" "golang.org/x/sys/execabs" ) // ErrorSpec specifies the error we care about. type ErrorSpec struct { // Errno is the error name as an errno value (e.g., ECONNREFUSED). Errno string // Failure is the error name according to OONI (e.g., FailureConnectionRefused). Failure string } // Specs contains all the error specs. var Specs = []ErrorSpec{{ Errno: "ECANCELED", Failure: "OperationCanceled", }, { Errno: "ECONNREFUSED", Failure: "ConnectionRefused", }, { Errno: "ECONNRESET", Failure: "ConnectionReset", }, { Errno: "EHOSTUNREACH", Failure: "HostUnreachable", }, { Errno: "ETIMEDOUT", Failure: "TimedOut", }, { Errno: "EAFNOSUPPORT", Failure: "AddressFamilyNotSupported", }, { Errno: "EADDRINUSE", Failure: "AddressInUse", }, { Errno: "EADDRNOTAVAIL", Failure: "AddressNotAvailable", }, { Errno: "EISCONN", Failure: "AlreadyConnected", }, { Errno: "EFAULT", Failure: "BadAddress", }, { Errno: "EBADF", Failure: "BadFileDescriptor", }, { Errno: "ECONNABORTED", Failure: "ConnectionAborted", }, { Errno: "EALREADY", Failure: "ConnectionAlreadyInProgress", }, { Errno: "EDESTADDRREQ", Failure: "DestinationAddressRequired", }, { Errno: "EINTR", Failure: "Interrupted", }, { Errno: "EINVAL", Failure: "InvalidArgument", }, { Errno: "EMSGSIZE", Failure: "MessageSize", }, { Errno: "ENETDOWN", Failure: "NetworkDown", }, { Errno: "ENETRESET", Failure: "NetworkReset", }, { Errno: "ENETUNREACH", Failure: "NetworkUnreachable", }, { Errno: "ENOBUFS", Failure: "NoBufferSpace", }, { Errno: "ENOPROTOOPT", Failure: "NoProtocolOption", }, { Errno: "ENOTSOCK", Failure: "NotASocket", }, { Errno: "ENOTCONN", Failure: "NotConnected", }, { Errno: "EWOULDBLOCK", Failure: "OperationWouldBlock", }, { Errno: "EACCES", Failure: "PermissionDenied", }, { Errno: "EPROTONOSUPPORT", Failure: "ProtocolNotSupported", }, { Errno: "EPROTOTYPE", Failure: "WrongProtocolType", }} func fileCreate(filename string) *os.File { filep, err := os.Create(filename) if err != nil { log.Fatal(err) } return filep } func fileWrite(filep *os.File, content string) { if _, err := filep.WriteString(content); err != nil { log.Fatal(err) } } func fileClose(filep *os.File) { if err := filep.Close(); err != nil { log.Fatal(err) } } func filePrintf(filep *os.File, format string, v ...interface{}) { fileWrite(filep, fmt.Sprintf(format, v...)) } func gofmt(filename string) { cmd := execabs.Command("go", "fmt", filename) if err := cmd.Run(); err != nil { log.Fatal(err) } } func writeSystemSpecificFile(kind string) { filename := "errno_" + kind + ".go" filep := fileCreate(filename) fileWrite(filep, "// Code generated by go generate; DO NOT EDIT.\n") filePrintf(filep, "// Generated: %+v\n\n", time.Now()) fileWrite(filep, "package errorsx\n\n") filePrintf(filep, "import \"golang.org/x/sys/%s\"\n\n", kind) fileWrite(filep, "const (\n") for _, spec := range Specs { filePrintf(filep, "\t%s = %s.%s\n", spec.Errno, kind, spec.Errno) } fileWrite(filep, ")\n\n") fileClose(filep) gofmt(filename) } func writeGenericFile() { filename := "errno.go" filep := fileCreate(filename) fileWrite(filep, "// Code generated by go generate; DO NOT EDIT.\n") filePrintf(filep, "// Generated: %+v\n\n", time.Now()) fileWrite(filep, "package errorsx\n\n") fileWrite(filep, "//go:generate go run ./generator/\n\n") fileWrite(filep, "import (\n") fileWrite(filep, "\t\"errors\"\n") fileWrite(filep, "\t\"syscall\"\n") fileWrite(filep, ")\n\n") fileWrite(filep, "// This enumeration lists the syscall-derived failures defined at\n") fileWrite(filep, "// https://github.com/ooni/spec/blob/master/data-formats/df-007-errors.md\n") fileWrite(filep, "//\n") fileWrite(filep, "// See also the enumeration at failures.go for the failures that\n") fileWrite(filep, "// DO NOT derive from system call errors.\n") fileWrite(filep, "const (\n") for _, spec := range Specs { filePrintf(filep, "\tFailure%s = \"%s\"\n", spec.Failure, strcase.ToSnake(spec.Failure)) } fileWrite(filep, ")\n\n") fileWrite(filep, "// toSyscallErr converts a syscall error to the\n") fileWrite(filep, "// proper OONI error. Returns the OONI error string\n") fileWrite(filep, "// on success, an empty string otherwise.\n") fileWrite(filep, "func toSyscallErr(err error) string {\n") fileWrite(filep, "\t// filter out system errors: necessary to detect all windows errors\n") fileWrite(filep, "\t// https://github.com/ooni/probe/issues/1526 describes the problem\n") fileWrite(filep, "\t// of mapping localized windows errors.\n") fileWrite(filep, "\tvar errno syscall.Errno\n") fileWrite(filep, "\tif !errors.As(err, &errno) {\n") fileWrite(filep, "\t\treturn \"\"\n") fileWrite(filep, "\t}\n") fileWrite(filep, "\tswitch errno {\n") for _, spec := range Specs { if spec.Failure != "" { filePrintf(filep, "\tcase %s:\n", spec.Errno) filePrintf(filep, "\t\treturn Failure%s\n", spec.Failure) } } fileWrite(filep, "\t}\n") fileWrite(filep, "\treturn \"\"\n") fileWrite(filep, "}\n\n") fileClose(filep) gofmt(filename) } func writeGenericTestFile() { filename := "errno_test.go" filep := fileCreate(filename) fileWrite(filep, "// Code generated by go generate; DO NOT EDIT.\n") filePrintf(filep, "// Generated: %+v\n\n", time.Now()) fileWrite(filep, "package errorsx\n\n") fileWrite(filep, "import (\n") fileWrite(filep, "\t\"io\"\n") fileWrite(filep, "\t\"syscall\"\n") fileWrite(filep, "\t\"testing\"\n") fileWrite(filep, ")\n\n") fileWrite(filep, "func TestToSyscallErr(t *testing.T) {\n") fileWrite(filep, "\tif v := toSyscallErr(io.EOF); v != \"\" {\n") fileWrite(filep, "\t\tt.Fatalf(\"expected empty string, got '%s'\", v)\n") fileWrite(filep, "\t}\n") for _, spec := range Specs { filePrintf(filep, "\tif v := toSyscallErr(%s); v != Failure%s {\n", spec.Errno, spec.Failure) filePrintf(filep, "\t\tt.Fatalf(\"expected '%%s', got '%%s'\", Failure%s, v)\n", spec.Failure) fileWrite(filep, "\t}\n") } fileWrite(filep, "\tif v := toSyscallErr(syscall.Errno(0)); v != \"\" {\n") fileWrite(filep, "\t\tt.Fatalf(\"expected empty string, got '%s'\", v)\n") fileWrite(filep, "\t}\n") fileWrite(filep, "}\n") fileClose(filep) gofmt(filename) } func main() { writeSystemSpecificFile("unix") writeSystemSpecificFile("windows") writeGenericFile() writeGenericTestFile() }