diff --git a/internal/errorsx/errno.go b/internal/errorsx/errno.go new file mode 100644 index 0000000..abc19bb --- /dev/null +++ b/internal/errorsx/errno.go @@ -0,0 +1,37 @@ +// Code generated by go generate; DO NOT EDIT. +// Generated: 2021-07-02 15:15:17.997258 +0200 CEST m=+0.110031584 + +package errorsx + +//go:generate go run ./generator/ + +import ( + "errors" + "syscall" +) + +// toSyscallErr converts a syscall error to the +// proper OONI error. Returns the OONI error string +// on success, an empty string otherwise. +func toSyscallErr(err error) string { + // filter out system errors: necessary to detect all windows errors + // https://github.com/ooni/probe/issues/1526 describes the problem + // of mapping localized windows errors. + var errno syscall.Errno + if !errors.As(err, &errno) { + return "" + } + switch errno { + case ECANCELED: + return FailureInterrupted + case ECONNREFUSED: + return FailureConnectionRefused + case ECONNRESET: + return FailureConnectionReset + case EHOSTUNREACH: + return FailureHostUnreachable + case ETIMEDOUT: + return FailureGenericTimeoutError + } + return "" +} diff --git a/internal/errorsx/errno_unix.go b/internal/errorsx/errno_unix.go index 82e6ede..3b4cc50 100644 --- a/internal/errorsx/errno_unix.go +++ b/internal/errorsx/errno_unix.go @@ -1,3 +1,6 @@ +// Code generated by go generate; DO NOT EDIT. +// Generated: 2021-07-02 15:15:17.887594 +0200 CEST m=+0.000365001 + package errorsx import "golang.org/x/sys/unix" diff --git a/internal/errorsx/errno_windows.go b/internal/errorsx/errno_windows.go index 730efc6..8e7f08f 100644 --- a/internal/errorsx/errno_windows.go +++ b/internal/errorsx/errno_windows.go @@ -1,3 +1,6 @@ +// Code generated by go generate; DO NOT EDIT. +// Generated: 2021-07-02 15:15:17.971155 +0200 CEST m=+0.083928418 + package errorsx import "golang.org/x/sys/windows" diff --git a/internal/errorsx/errorsx.go b/internal/errorsx/errorsx.go index d8dd3ad..88e767e 100644 --- a/internal/errorsx/errorsx.go +++ b/internal/errorsx/errorsx.go @@ -6,7 +6,6 @@ import ( "errors" "fmt" "strings" - "syscall" "github.com/ooni/probe-cli/v3/internal/scrubber" ) @@ -112,24 +111,10 @@ func toFailureString(err error) string { return errwrapper.Error() // we've already wrapped it } - // filter out system errors: necessary to detect all windows errors - // https://github.com/ooni/probe/issues/1526 describes the problem of mapping localized windows errors - var errno syscall.Errno - if errors.As(err, &errno) { - switch errno { - case ECANCELED: - return FailureInterrupted - case ECONNRESET: - return FailureConnectionReset - case ECONNREFUSED: - return FailureConnectionRefused - case EHOSTUNREACH: - return FailureHostUnreachable - case ETIMEDOUT: - return FailureGenericTimeoutError - // TODO(kelmenhorst): find out if we need more system errors here - } + if failure := toSyscallErr(err); failure != "" { + return failure } + if errors.Is(err, context.Canceled) { return FailureInterrupted } diff --git a/internal/errorsx/generator/main.go b/internal/errorsx/generator/main.go new file mode 100644 index 0000000..2c5019b --- /dev/null +++ b/internal/errorsx/generator/main.go @@ -0,0 +1,175 @@ +package main + +import ( + "fmt" + "log" + "os" + "time" + + "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 +} + +// TODO(kelmenhorst): find out if we need more system errors here. Currently +// the code does not generate any mapping if Failure is empty. + +// Specs contains all the error specs. +var Specs = []ErrorSpec{{ + Errno: "ECANCELED", + Failure: "Interrupted", +}, { + Errno: "ECONNREFUSED", + Failure: "ConnectionRefused", +}, { + Errno: "ECONNRESET", + Failure: "ConnectionReset", +}, { + Errno: "EHOSTUNREACH", + Failure: "HostUnreachable", +}, { + Errno: "ETIMEDOUT", + Failure: "GenericTimeoutError", +}, { + Errno: "EAFNOSUPPORT", +}, { + Errno: "EADDRINUSE", +}, { + Errno: "EADDRNOTAVAIL", +}, { + Errno: "EISCONN", +}, { + Errno: "EFAULT", +}, { + Errno: "EBADF", +}, { + Errno: "ECONNABORTED", +}, { + Errno: "EALREADY", +}, { + Errno: "EDESTADDRREQ", +}, { + Errno: "EINTR", +}, { + Errno: "EINVAL", +}, { + Errno: "EMSGSIZE", +}, { + Errno: "ENETDOWN", +}, { + Errno: "ENETRESET", +}, { + Errno: "ENETUNREACH", +}, { + Errno: "ENOBUFS", +}, { + Errno: "ENOPROTOOPT", +}, { + Errno: "ENOTSOCK", +}, { + Errno: "ENOTCONN", +}, { + Errno: "EWOULDBLOCK", +}, { + Errno: "EACCES", +}, { + Errno: "EPROTONOSUPPORT", +}, { + Errno: "EPROTOTYPE", +}} + +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, "// 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 main() { + writeSystemSpecificFile("unix") + writeSystemSpecificFile("windows") + writeGenericFile() +}